<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/Frog_32px_1177822_easyicon.net.ico">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/Frog_16px_1177822_easyicon.net.ico">
  <link rel="mask-icon" href="/images/Frog_32px_1177822_easyicon.net.ico" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib/pace/pace-theme-minimal.min.css">
  <script src="/lib/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"hxy1997.xyz","root":"/","scheme":"Pisces","version":"7.8.0","exturl":false,"sidebar":{"position":"left","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":true,"pangu":false,"comments":{"style":"tabs","active":"valine","storage":true,"lazyload":true,"nav":null,"activeClass":"valine"},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"输入关键字","hits_empty":"没有找到与「${query}」相关搜索","hits_stats":"${hits} 条相关记录，共耗时 ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.json"};
  </script>

  <meta name="description" content="去看新版本吧，这一版本不折腾了，好多抄的，自己都没有整明白。 手写常见js函数，面试必备，多练几遍，争取手撕。">
<meta property="og:type" content="article">
<meta property="og:title" content="js函数手写（旧版）">
<meta property="og:url" content="https://hxy1997.xyz/2020/10/18/js%E5%87%BD%E6%95%B0%E6%89%8B%E5%86%99%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89/index.html">
<meta property="og:site_name" content="hxy的博客">
<meta property="og:description" content="去看新版本吧，这一版本不折腾了，好多抄的，自己都没有整明白。 手写常见js函数，面试必备，多练几遍，争取手撕。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428112307.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428111557.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428120949.png">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514223513.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514223714.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428141501.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428144007.gif">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514224321.gif">
<meta property="og:image" content="https://ask.qcloudimg.com/http-save/yehe-3713434/sf9725l2cd.jpeg?imageView2/2/w/1620">
<meta property="og:image" content="https://ask.qcloudimg.com/http-save/yehe-3713434/nb0dwyns7q.jpeg?imageView2/2/w/1620">
<meta property="og:image" content="https://ask.qcloudimg.com/http-save/yehe-3713434/fh73smmild.jpeg?imageView2/2/w/1620">
<meta property="og:image" content="https://ask.qcloudimg.com/http-save/yehe-3713434/xtb15ikjai.jpeg?imageView2/2/w/1620">
<meta property="og:image" content="https://ask.qcloudimg.com/http-save/yehe-3713434/im20c61hq4.jpeg?imageView2/2/w/1620">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514224540.png">
<meta property="article:published_time" content="2020-10-17T16:00:00.000Z">
<meta property="article:modified_time" content="2021-05-14T14:48:06.453Z">
<meta property="article:author" content="hxy">
<meta property="article:tag" content="javascript">
<meta property="article:tag" content="ES6">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428112307.gif">

<link rel="canonical" href="https://hxy1997.xyz/2020/10/18/js%E5%87%BD%E6%95%B0%E6%89%8B%E5%86%99%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>js函数手写（旧版） | hxy的博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="hxy的博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">hxy的博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Mia san Mia!</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/huxingyi1997" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://hxy1997.xyz/2020/10/18/js%E5%87%BD%E6%95%B0%E6%89%8B%E5%86%99%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/Robben.gif">
      <meta itemprop="name" content="hxy">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="hxy的博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          js函数手写（旧版）
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2020-10-18 00:00:00" itemprop="dateCreated datePublished" datetime="2020-10-18T00:00:00+08:00">2020-10-18</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-05-14 22:48:06" itemprop="dateModified" datetime="2021-05-14T22:48:06+08:00">2021-05-14</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/web%E5%89%8D%E7%AB%AF/" itemprop="url" rel="index"><span itemprop="name">web前端</span></a>
                </span>
            </span>

          
            <span class="post-meta-item" title="热度" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">热度：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/2020/10/18/js%E5%87%BD%E6%95%B0%E6%89%8B%E5%86%99%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/2020/10/18/js%E5%87%BD%E6%95%B0%E6%89%8B%E5%86%99%EF%BC%88%E6%97%A7%E7%89%88%EF%BC%89/" itemprop="commentCount"></span>
    </a>
  </span>
  
  

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>去看新版本吧，这一版本不折腾了，好多抄的，自己都没有整明白。</p>
<p>手写常见js函数，面试必备，多练几遍，争取手撕。</p>
<span id="more"></span>

<h2 id="1-手动实现call-apply-bind"><a href="#1-手动实现call-apply-bind" class="headerlink" title="1.手动实现call,apply,bind"></a>1.手动实现call,apply,bind</h2><h3 id="模拟实现call"><a href="#模拟实现call" class="headerlink" title="模拟实现call"></a>模拟实现call</h3><p>1.判断当前<code>this</code>是否为函数，防止<code>Function.prototype.myCall()</code> 直接调用</p>
<p>2.<code>context</code> 为可选参数，如果不传的话默认上下文为 <code>window</code></p>
<p>3.为<code>context</code> 创建一个 <code>Symbol</code>（保证不会重名）属性，将当前函数赋值给这个属性</p>
<p>4.处理参数，传入第一个参数后的其余参数</p>
<p>5.调用函数后即删除该<code>Symbol</code>属性</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* context为函数运行时要使用的this值 默认将context指向window 注意仅在非严格模式下会有这种行为</span></span><br><span class="line"><span class="comment">* args 为一个数组或者类数组对象，是调用函数时的参数列表</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="built_in">Function</span>.prototype.myCall = <span class="function"><span class="keyword">function</span> (<span class="params">context = <span class="built_in">window</span>, ...args</span>) </span>&#123;</span><br><span class="line">	<span class="comment">// 用于防止 Function.prototype.myCall() 直接调用</span></span><br><span class="line">	<span class="keyword">if</span> (<span class="built_in">this</span> === <span class="built_in">Function</span>.prototype) <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">	<span class="comment">// context = context || window;</span></span><br><span class="line">    <span class="comment">// 为了防止原来的属性被覆盖，用Symbol去创建一个独一无二的值</span></span><br><span class="line">	<span class="keyword">let</span> fn = <span class="built_in">Symbol</span>();</span><br><span class="line">    <span class="comment">// 这里this指向调用myCall的函数</span></span><br><span class="line">	context[fn] = <span class="built_in">this</span>;</span><br><span class="line">    <span class="comment">// 重点代码，利用this指向，调用myCall的函数,并接收返回值</span></span><br><span class="line">	<span class="keyword">let</span> result = context[fn](...args);</span><br><span class="line">    <span class="comment">// 最后删除这个临时属性</span></span><br><span class="line">	<span class="keyword">delete</span> context[fn];</span><br><span class="line">    <span class="comment">// 返回结果</span></span><br><span class="line">	<span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="模拟实现apply"><a href="#模拟实现apply" class="headerlink" title="模拟实现apply"></a>模拟实现apply</h3><p><code>apply</code>实现类似<code>call</code>，参数为数组</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* context为函数运行时要使用的this值 默认将context指向window 注意仅在非严格模式下会有这种行为</span></span><br><span class="line"><span class="comment">* args 为一个数组或者类数组对象，是调用函数时的参数列表</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="built_in">Function</span>.prototype.myApply = <span class="function"><span class="keyword">function</span> (<span class="params">context = <span class="built_in">window</span>, args</span>) </span>&#123;</span><br><span class="line">	<span class="comment">// 用于防止 Function.prototype.myApply() 直接调用</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span> === <span class="built_in">Function</span>.prototype) <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">    <span class="comment">// context = context || window;</span></span><br><span class="line">    <span class="comment">// 为了防止原来的属性被覆盖，用Symbol去创建一个独一无二的值</span></span><br><span class="line">    <span class="keyword">let</span> fn = <span class="built_in">Symbol</span>();</span><br><span class="line">    <span class="comment">// 这里this指向调用myApply的函数</span></span><br><span class="line">    context[fn] = <span class="built_in">this</span>;</span><br><span class="line">    <span class="comment">// 重点代码，利用this指向，相当于context.caller(...args)</span></span><br><span class="line">    <span class="keyword">let</span> result = context[fn](...args);</span><br><span class="line">    <span class="comment">// 最后删除这个临时属性</span></span><br><span class="line">    <span class="keyword">delete</span> context[fn];</span><br><span class="line">    <span class="comment">// 返回结果</span></span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="模拟实现bind"><a href="#模拟实现bind" class="headerlink" title="模拟实现bind"></a>模拟实现bind</h3><p>1.处理参数，返回一个闭包</p>
<p>2.判断是否为构造函数调用，如果是则使用<code>new</code>调用当前函数</p>
<p>3.如果不是，使用<code>apply</code>，将<code>context</code>和处理好的参数传入</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.myBind = <span class="function"><span class="keyword">function</span> (<span class="params">context = <span class="built_in">window</span>, ...args</span>) </span>&#123;</span><br><span class="line">	<span class="keyword">if</span> (<span class="built_in">this</span> === <span class="built_in">Function</span>.prototype) <span class="keyword">return</span> <span class="literal">undefined</span>;</span><br><span class="line">	<span class="keyword">const</span> _this = <span class="built_in">this</span></span><br><span class="line">    <span class="comment">// 返回一个函数</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="title">F</span>(<span class="params">...<span class="built_in">arguments</span></span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 因为返回了一个函数，我们可以 new F()，所以需要判断</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span> <span class="keyword">instanceof</span> F) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> _this(...args, ...arguments)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> _this.apply(context, args.concat(...arguments))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="面试够用版"><a href="#面试够用版" class="headerlink" title="面试够用版"></a>面试够用版</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;*</span><br><span class="line">* context为函数运行时要使用的this值 默认将context指向window 注意仅在非严格模式下会有这种行为</span><br><span class="line">* args 为一个数组或者类数组对象，是调用函数时的参数列表</span><br><span class="line">*&#x2F;</span><br><span class="line">Function.prototype.myBind &#x3D; function (context &#x3D; window, ...args) &#123;</span><br><span class="line">    </span><br><span class="line">    &#x2F;&#x2F; 为了防止原来的属性被覆盖，用Symbol去创建一个独一无二的值</span><br><span class="line">    let fn &#x3D; Symbol();</span><br><span class="line">    &#x2F;&#x2F; 这里this指向调用myApply的函数</span><br><span class="line">    context[fn] &#x3D; this;</span><br><span class="line">    &#x2F;&#x2F; 返回闭包函数</span><br><span class="line">    return function (..._args) &#123;</span><br><span class="line">        &#x2F;&#x2F; 与当前参数组合</span><br><span class="line">        args &#x3D; args.concat(_args);</span><br><span class="line">        &#x2F;&#x2F; 重点代码，执行函数</span><br><span class="line">        context[fn](...args);</span><br><span class="line">        &#x2F;&#x2F; 最后删除这个临时属性</span><br><span class="line">        delete context[fn];   </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h3><p>获取函数中的参数：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取argument对象 类数组对象 不能调用数组方法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">test1</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;获取argument对象 类数组对象 不能调用数组方法&#x27;</span>, <span class="built_in">arguments</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取参数数组  可以调用数组方法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">test2</span>(<span class="params">...args</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;获取参数数组  可以调用数组方法&#x27;</span>, args);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取除第一个参数的剩余参数数组</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">test3</span>(<span class="params">first, ...args</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;获取argument对象 类数组对象 不能调用数组方法&#x27;</span>, args);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 透传参数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">test4</span>(<span class="params">first, ...args</span>) </span>&#123;</span><br><span class="line">    fn(...args);</span><br><span class="line">    fn(...arguments);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;透传&#x27;</span>, ...arguments);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">test1(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">test2(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">test3(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">test4(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br></pre></td></tr></table></figure>



<h2 id="2-观察者模式"><a href="#2-观察者模式" class="headerlink" title="2.观察者模式"></a>2.观察者模式</h2><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul>
<li>可以广泛应用于异步编程，它可以代替我们传统的回调函数</li>
<li>我们不需要关注对象在异步执行阶段的内部状态，我们只关心事件完成的时间点</li>
<li>取代对象之间硬编码通知机制，一个对象不必显式调用另一个对象的接口，而是松耦合的联系在一起 。</li>
<li>虽然不知道彼此的细节，但不影响相互通信。更重要的是，其中一个对象改变不会影响另一个对象。</li>
</ul>
<h3 id="Nodejs的EventEmitter"><a href="#Nodejs的EventEmitter" class="headerlink" title="Nodejs的EventEmitter"></a>Nodejs的EventEmitter</h3><p><code>Nodejs</code>的<code>EventEmitter</code>就是观察者模式的典型实现，<code>Nodejs</code>的<code>events</code>模块只提供了一个对象： <code>events.EventEmitter``。EventEmitter</code> 的核心就是事件触发与事件监听器功能的封装。</p>
<blockquote>
<p>Node.js 里面的许多对象都会分发事件：一个 net.Server 对象会在每次有新连接时触发一个事件， 一个 fs.readStream 对象会在文件被打开的时候触发一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。</p>
</blockquote>
<h3 id="Api"><a href="#Api" class="headerlink" title="Api"></a>Api</h3><p><strong>addListener(event, listener)</strong></p>
<p>为指定事件添加一个监听器，默认添加到监听器数组的尾部。</p>
<p><strong>removeListener(event, listener)</strong></p>
<p>移除指定事件的某个监听器，监听器必须是该事件已经注册过的监听器。它接受两个参数，第一个是事件名称，第二个是回调函数名称。</p>
<p><strong>setMaxListeners(n)</strong></p>
<p>默认情况下， EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于提高监听器的默认限制的数量。</p>
<p><strong>once(event, listener)</strong></p>
<p>为指定事件注册一个单次监听器，即 监听器最多只会触发一次，触发后立刻解除该监听器。</p>
<p><strong>emit(event, [arg1], [arg2], […])</strong></p>
<p>按监听器的顺序执行执行每个监听器，如果事件有注册监听返回 <code>true</code>，否则返回 <code>false</code>。</p>
<h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> events = <span class="built_in">require</span>(<span class="string">&#x27;events&#x27;</span>);</span><br><span class="line"><span class="keyword">var</span> eventEmitter = <span class="keyword">new</span> events.EventEmitter();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听器 #1</span></span><br><span class="line"><span class="keyword">var</span> listener1 = <span class="function"><span class="keyword">function</span> <span class="title">listener1</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">   <span class="built_in">console</span>.log(<span class="string">&#x27;监听器 listener1 执行。&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听器 #2</span></span><br><span class="line"><span class="keyword">var</span> listener2 = <span class="function"><span class="keyword">function</span> <span class="title">listener2</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">&#x27;监听器 listener2 执行。&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 绑定 connection 事件，处理函数为 listener1 </span></span><br><span class="line">eventEmitter.addListener(<span class="string">&#x27;connection&#x27;</span>, listener1);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 绑定 connection 事件，调用一次，处理函数为 listener2</span></span><br><span class="line">eventEmitter.once(<span class="string">&#x27;connection&#x27;</span>, listener2);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理 connection 事件 </span></span><br><span class="line">eventEmitter.emit(<span class="string">&#x27;connection&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理 connection 事件 </span></span><br><span class="line">eventEmitter.emit(<span class="string">&#x27;connection&#x27;</span>);</span><br></pre></td></tr></table></figure>

<h3 id="手动实现EventEmitter"><a href="#手动实现EventEmitter" class="headerlink" title="手动实现EventEmitter"></a>手动实现EventEmitter</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">EventEmitter</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">this</span>._maxListeners = <span class="number">10</span>;</span><br><span class="line">    <span class="built_in">this</span>._events = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向事件队列添加事件</span></span><br><span class="line"><span class="comment">// prepend为true表示向事件队列头部添加事件</span></span><br><span class="line">EventEmitter.prototype.addListener = <span class="function"><span class="keyword">function</span> (<span class="params">type, listener, prepend</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">this</span>._events) &#123;</span><br><span class="line">    	<span class="built_in">this</span>._events = <span class="built_in">Object</span>.create(<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>._events[type]) &#123;</span><br><span class="line">        <span class="keyword">if</span> (prepend) &#123;</span><br><span class="line">        	<span class="built_in">this</span>._events[type].unshift(listener);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>._events[type].push(listener);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    	<span class="built_in">this</span>._events[type] = [listener];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 移除某个事件</span></span><br><span class="line">EventEmitter.prototype.removeListener = <span class="function"><span class="keyword">function</span> (<span class="params">type, listener</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(<span class="built_in">this</span>._events[type])) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!listener) &#123;</span><br><span class="line">        	<span class="keyword">delete</span> <span class="built_in">this</span>._events[type]</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        	<span class="built_in">this</span>._events[type] = <span class="built_in">this</span>._events[type].filter(<span class="function"><span class="params">e</span> =&gt;</span> e !== listener &amp;&amp; e.origin !== listener)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 向事件队列添加事件，只执行一次</span></span><br><span class="line">EventEmitter.prototype.once = <span class="function"><span class="keyword">function</span> (<span class="params">type, listener</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> only = <span class="function">(<span class="params">...args</span>) =&gt;</span> &#123;</span><br><span class="line">        listener.apply(<span class="built_in">this</span>, args);</span><br><span class="line">        <span class="built_in">this</span>.removeListener(type, listener);</span><br><span class="line">    &#125;</span><br><span class="line">    only.origin = listener;</span><br><span class="line">    <span class="built_in">this</span>.addListener(type, only);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行某类事件</span></span><br><span class="line">EventEmitter.prototype.emit = <span class="function"><span class="keyword">function</span> (<span class="params">type, ...args</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(<span class="built_in">this</span>._events[type])) &#123;</span><br><span class="line">        <span class="built_in">this</span>._events[type].forEach(<span class="function"><span class="params">fn</span> =&gt;</span> &#123;</span><br><span class="line">        	fn.apply(<span class="built_in">this</span>, args);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置最大事件监听个数</span></span><br><span class="line">EventEmitter.prototype.setMaxListeners = <span class="function"><span class="keyword">function</span> (<span class="params">count</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">this</span>.maxListeners = count;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>测试代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> emitter = <span class="keyword">new</span> EventEmitter();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> onceListener = <span class="function"><span class="keyword">function</span> (<span class="params">args</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;我只能被执行一次&#x27;</span>, args, <span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> listener = <span class="function"><span class="keyword">function</span> (<span class="params">args</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">&#x27;我是一个listener&#x27;</span>, args, <span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">emitter.once(<span class="string">&#x27;click&#x27;</span>, onceListener);</span><br><span class="line">emitter.addListener(<span class="string">&#x27;click&#x27;</span>, listener);</span><br><span class="line"></span><br><span class="line">emitter.emit(<span class="string">&#x27;click&#x27;</span>, <span class="string">&#x27;参数&#x27;</span>);</span><br><span class="line">emitter.emit(<span class="string">&#x27;click&#x27;</span>);</span><br><span class="line"></span><br><span class="line">emitter.removeListener(<span class="string">&#x27;click&#x27;</span>, listener);</span><br><span class="line">emitter.emit(<span class="string">&#x27;click&#x27;</span>);</span><br></pre></td></tr></table></figure>

<h3 id="JavaScript自定义事件"><a href="#JavaScript自定义事件" class="headerlink" title="JavaScript自定义事件"></a>JavaScript自定义事件</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//1、创建事件</span></span><br><span class="line"><span class="keyword">var</span> myEvent = <span class="keyword">new</span> Event(<span class="string">&quot;myEvent&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//2、注册事件监听器</span></span><br><span class="line">elem.addEventListener(<span class="string">&quot;myEvent&quot;</span>,<span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//3、触发事件</span></span><br><span class="line">elem.dispatchEvent(myEvent);</span><br></pre></td></tr></table></figure>



<h2 id="3-防抖-debounce"><a href="#3-防抖-debounce" class="headerlink" title="3.防抖(debounce)"></a>3.防抖(debounce)</h2><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>不管事件触发频率多高，一定在事件触发<code>n</code>秒后才执行，如果你在一个事件触发的 <code>n</code> 秒内又触发了这个事件，就以新的事件的时间为准，<code>n</code>秒后才执行，总之，触发完事件 <code>n</code> 秒内不再触发事件，<code>n</code>秒后再执行。</p>
<p>在前端开发中会遇到一些频繁的事件触发，比如：</p>
<ol>
<li>window 的 resize、scroll</li>
<li>mousedown、mousemove</li>
<li>keyup、keydown<br>……</li>
</ol>
<p>为此，我们举个示例代码来了解事件如何频繁的触发：</p>
<p>我们写个 <code>index.html</code> 文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">&lt;!DOCTYPE html&gt;</span><br><span class="line">&lt;html lang&#x3D;&quot;zh-cmn-Hans&quot;&gt;</span><br><span class="line"></span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta charset&#x3D;&quot;utf-8&quot;&gt;</span><br><span class="line">    &lt;meta http-equiv&#x3D;&quot;x-ua-compatible&quot; content&#x3D;&quot;IE&#x3D;edge, chrome&#x3D;1&quot;&gt;</span><br><span class="line">    &lt;title&gt;debounce&lt;&#x2F;title&gt;</span><br><span class="line">    &lt;style&gt;</span><br><span class="line">        #container&#123;</span><br><span class="line">            width: 100%;</span><br><span class="line">            height: 200px;</span><br><span class="line">            line-height: 200px;</span><br><span class="line">            text-align: center;</span><br><span class="line">            color: #fff;</span><br><span class="line">            background-color: #444;</span><br><span class="line">            font-size: 30px;</span><br><span class="line">        &#125;</span><br><span class="line">    &lt;&#x2F;style&gt;</span><br><span class="line">&lt;&#x2F;head&gt;</span><br><span class="line"></span><br><span class="line">&lt;body&gt;</span><br><span class="line">    &lt;div id&#x3D;&quot;container&quot;&gt;&lt;&#x2F;div&gt;</span><br><span class="line">    &lt;script src&#x3D;&quot;debounce.js&quot;&gt;&lt;&#x2F;script&gt;</span><br><span class="line">&lt;&#x2F;body&gt;</span><br><span class="line"></span><br><span class="line">&lt;&#x2F;html&gt;</span><br></pre></td></tr></table></figure>

<p><code>debounce.js</code> 文件的代码如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var count &#x3D; 1;</span><br><span class="line">var container &#x3D; document.getElementById(&#39;container&#39;);</span><br><span class="line"></span><br><span class="line">function getUserAction() &#123;</span><br><span class="line">    container.innerHTML &#x3D; count++;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">container.onmousemove &#x3D; getUserAction;</span><br></pre></td></tr></table></figure>

<p>我们来看看效果：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428112307.gif" alt="debounce"></p>
<p>从左边滑到右边就触发了 165 次 getUserAction 函数！</p>
<p>因为这个例子很简单，所以浏览器完全反应的过来，可是如果是复杂的回调函数或是 ajax 请求呢？假设 1 秒触发了 60 次，每个回调就必须在 1000 / 60 = 16.67ms 内完成，否则就会有卡顿出现。</p>
<p>为了解决这个问题，一般有两种解决方案：</p>
<ol>
<li>debounce 防抖</li>
<li>throttle 节流</li>
</ol>
<h3 id="防抖是什么"><a href="#防抖是什么" class="headerlink" title="防抖是什么"></a>防抖是什么</h3><p>今天重点讲讲防抖的实现。</p>
<p>防抖的原理就是：你尽管触发事件，但是我一定在事件触发 n 秒后才执行，如果你在一个事件触发的 n 秒内又触发了这个事件，那我就以新的事件的时间为准，n 秒后才执行，总之，就是要等你触发完事件 n 秒内不再触发事件，我才执行，真是任性呐!</p>
<h3 id="第一版"><a href="#第一版" class="headerlink" title="第一版"></a>第一版</h3><p>根据这段表述，我们可以写第一版的代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第一版</span><br><span class="line">function debounce(func, wait) &#123;</span><br><span class="line">    var timeout;</span><br><span class="line">    return function () &#123;</span><br><span class="line">        clearTimeout(timeout)</span><br><span class="line">        timeout &#x3D; setTimeout(func, wait);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果我们要使用它，以最一开始的例子为例：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">container.onmousemove &#x3D; debounce(getUserAction, 1000);</span><br></pre></td></tr></table></figure>

<p>现在随你怎么移动，反正你移动完 1000ms 内不再触发，我才执行事件。看看使用效果：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428111557.gif" alt="debounce 第一版"></p>
<p>顿时就从 165 次降低成了 1 次!</p>
<p>棒棒哒，我们接着完善它。</p>
<h3 id="this"><a href="#this" class="headerlink" title="this"></a>this</h3><p>如果我们在 <code>getUserAction</code> 函数中 <code>console.log(this)</code>，在不使用 <code>debounce</code> 函数的时候，<code>this</code> 的值为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;div id&#x3D;&quot;container&quot;&gt;&lt;&#x2F;div&gt;</span><br></pre></td></tr></table></figure>

<p>但是如果使用我们的 debounce 函数，this 就会指向 Window 对象！</p>
<p>所以我们需要将 this 指向正确的对象。</p>
<p>我们修改下代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第二版</span><br><span class="line">function debounce(func, wait) &#123;</span><br><span class="line">    var timeout;</span><br><span class="line"></span><br><span class="line">    return function () &#123;</span><br><span class="line">        var context &#x3D; this;</span><br><span class="line"></span><br><span class="line">        clearTimeout(timeout)</span><br><span class="line">        timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">            func.apply(context)</span><br><span class="line">        &#125;, wait);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在 this 已经可以正确指向了。让我们看下个问题：</p>
<h3 id="event-对象"><a href="#event-对象" class="headerlink" title="event 对象"></a>event 对象</h3><p>JavaScript 在事件处理函数中会提供事件对象 event，我们修改下 getUserAction 函数：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function getUserAction(e) &#123;</span><br><span class="line">    console.log(e);</span><br><span class="line">    container.innerHTML &#x3D; count++;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>如果我们不使用 debouce 函数，这里会打印 MouseEvent 对象，如图所示：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428120949.png" alt="MouseEvent"></p>
<p>但是在我们实现的 debounce 函数中，却只会打印 undefined!</p>
<p>所以我们再修改一下代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第三版</span><br><span class="line">function debounce(func, wait) &#123;</span><br><span class="line">    var timeout;</span><br><span class="line"></span><br><span class="line">    return function () &#123;</span><br><span class="line">        var context &#x3D; this;</span><br><span class="line">        var args &#x3D; arguments;</span><br><span class="line"></span><br><span class="line">        clearTimeout(timeout)</span><br><span class="line">        timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">            func.apply(context, args)</span><br><span class="line">        &#125;, wait);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到此为止，我们修复了两个小问题：</p>
<ol>
<li>this 指向</li>
<li>event 对象</li>
</ol>
<h3 id="立刻执行"><a href="#立刻执行" class="headerlink" title="立刻执行"></a>立刻执行</h3><p>这个时候，代码已经很是完善了，但是为了让这个函数更加完善，我们接下来思考一个新的需求。</p>
<p>这个需求就是：</p>
<p>我不希望非要等到事件停止触发后才执行，我希望立刻执行函数，然后等到停止触发 n 秒后，才可以重新触发执行。</p>
<p>想想这个需求也是很有道理的嘛，那我们加个 immediate 参数判断是否是立刻执行。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第四版</span><br><span class="line">function debounce(func, wait, immediate) &#123;</span><br><span class="line">    var timeout;</span><br><span class="line">    </span><br><span class="line">    return function () &#123;</span><br><span class="line">        var context &#x3D; this;</span><br><span class="line">        var args &#x3D; arguments;</span><br><span class="line"></span><br><span class="line">        if (timeout) clearTimeout(timeout);</span><br><span class="line">        if (immediate) &#123;</span><br><span class="line">            &#x2F;&#x2F; 如果已经执行过，不再执行</span><br><span class="line">            var callNow &#x3D; !timeout;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">            &#125;, wait)</span><br><span class="line">            if (callNow) func.apply(context, args)</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                func.apply(context, args)</span><br><span class="line">            &#125;, wait);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>再来看看使用效果：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514223513.gif" alt="debounce 第四版"></p>
<h3 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h3><p>此时注意一点，就是 getUserAction 函数可能是有返回值的，所以我们也要返回函数的执行结果，但是当 immediate 为 false 的时候，因为使用了 setTimeout ，我们将 func.apply(context, args) 的返回值赋给变量，最后再 return 的时候，值将会一直是 undefined，所以我们只在 immediate 为 true 的时候返回函数的执行结果。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第五版</span><br><span class="line">function debounce(func, wait, immediate) &#123;</span><br><span class="line"></span><br><span class="line">    var timeout, result;</span><br><span class="line"></span><br><span class="line">    return function () &#123;</span><br><span class="line">        var context &#x3D; this;</span><br><span class="line">        var args &#x3D; arguments;</span><br><span class="line"></span><br><span class="line">        if (timeout) clearTimeout(timeout);</span><br><span class="line">        if (immediate) &#123;</span><br><span class="line">            &#x2F;&#x2F; 如果已经执行过，不再执行</span><br><span class="line">            var callNow &#x3D; !timeout;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">            &#125;, wait)</span><br><span class="line">            if (callNow) result &#x3D; func.apply(context, args)</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                func.apply(context, args)</span><br><span class="line">            &#125;, wait);</span><br><span class="line">        &#125;</span><br><span class="line">        return result;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="取消"><a href="#取消" class="headerlink" title="取消"></a>取消</h3><p>最后我们再思考一个小需求，我希望能取消 debounce 函数，比如说我 debounce 的时间间隔是 10 秒钟，immediate 为 true，这样的话，我只有等 10 秒后才能重新触发事件，现在我希望有一个按钮，点击后，取消防抖，这样我再去触发，就可以又立刻执行啦，是不是很开心？</p>
<p>为了这个需求，我们写最后一版的代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第六版</span><br><span class="line">function debounce(func, wait, immediate) &#123;</span><br><span class="line">    var timeout, result;</span><br><span class="line"></span><br><span class="line">    var debounced &#x3D; function () &#123;</span><br><span class="line">        var context &#x3D; this;</span><br><span class="line">        var args &#x3D; arguments;</span><br><span class="line"></span><br><span class="line">        if (timeout) clearTimeout(timeout);</span><br><span class="line">        if (immediate) &#123;</span><br><span class="line">            &#x2F;&#x2F; 如果已经执行过，不再执行</span><br><span class="line">            var callNow &#x3D; !timeout;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">            &#125;, wait)</span><br><span class="line">            if (callNow) result &#x3D; func.apply(context, args)</span><br><span class="line">        &#125;</span><br><span class="line">        else &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                func.apply(context, args)</span><br><span class="line">            &#125;, wait);</span><br><span class="line">        &#125;</span><br><span class="line">        return result;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    debounced.cancel &#x3D; function() &#123;</span><br><span class="line">        clearTimeout(timeout);</span><br><span class="line">        timeout &#x3D; null;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    return debounced;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>那么该如何使用这个 cancel 函数呢？依然是以上面的 demo 为例：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var count &#x3D; 1;</span><br><span class="line">var container &#x3D; document.getElementById(&#39;container&#39;);</span><br><span class="line"></span><br><span class="line">function getUserAction(e) &#123;</span><br><span class="line">    container.innerHTML &#x3D; count++;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var setUseAction &#x3D; debounce(getUserAction, 10000, true);</span><br><span class="line"></span><br><span class="line">container.onmousemove &#x3D; setUseAction;</span><br><span class="line"></span><br><span class="line">document.getElementById(&quot;button&quot;).addEventListener(&#39;click&#39;, function()&#123;</span><br><span class="line">    setUseAction.cancel();</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>演示效果如下：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514223714.gif" alt="debounce-cancel"></p>
<p>至此我们已经完整实现了一个 underscore 中的 debounce 函数，恭喜，撒花！</p>
<h3 id="面试版代码"><a href="#面试版代码" class="headerlink" title="面试版代码"></a>面试版代码</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// func是用户传入需要防抖的函数</span></span><br><span class="line"><span class="comment">// wait是等待时间</span></span><br><span class="line"><span class="keyword">const</span> debounce = <span class="function"><span class="keyword">function</span>(<span class="params">func, wait = <span class="number">50</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 缓存一个定时器id</span></span><br><span class="line">    <span class="keyword">let</span> timer = <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// 这里返回的函数是每次用户实际调用的防抖函数</span></span><br><span class="line">    <span class="comment">// 如果已经设定过定时器了就清空上一次的定时器</span></span><br><span class="line">    <span class="comment">// 开始一个新的定时器，延迟执行用户传入的方法</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">...args</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (timer) <span class="built_in">clearTimeout</span>(timer);</span><br><span class="line">        timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            func.apply(<span class="built_in">this</span>, args);</span><br><span class="line">        &#125;, wait)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><strong>适用场景：</strong></p>
<blockquote>
<p>按钮提交场景：防止多次提交按钮，只执行最后提交的一次 服务端验证场景：表单验证需要服务端配合，只执行一段连续的输入事件的最后一次，还有搜索联想词功能类似</p>
</blockquote>
<h2 id="4-节流-throttle"><a href="#4-节流-throttle" class="headerlink" title="4.节流(throttle)"></a>4.节流(throttle)</h2><h3 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a>原理</h3><p>规定在一个单位时间内，只能触发一次函数。如果这个单位时间内触发多次函数，只有一次生效</p>
<h3 id="节流是什么"><a href="#节流是什么" class="headerlink" title="节流是什么"></a>节流是什么</h3><p>节流的原理很简单：</p>
<p>如果你持续触发事件，每隔一段时间，只执行一次事件。</p>
<p>根据首次是否执行以及结束后是否执行，效果有所不同，实现的方式也有所不同。<br>我们用 leading 代表首次是否执行，trailing 代表结束后是否再执行一次。</p>
<p>关于节流的实现，有两种主流的实现方式，一种是使用时间戳，一种是设置定时器。</p>
<h3 id="使用时间戳"><a href="#使用时间戳" class="headerlink" title="使用时间戳"></a>使用时间戳</h3><p>让我们来看第一种方法：使用时间戳，当触发事件的时候，我们取出当前的时间戳，然后减去之前的时间戳(最一开始值设为 0 )，如果大于设置的时间周期，就执行函数，然后更新时间戳为当前的时间戳，如果小于，就不执行。</p>
<p>看了这个表述，是不是感觉已经可以写出代码了…… 让我们来写第一版的代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第一版</span><br><span class="line">function throttle(func, wait) &#123;</span><br><span class="line">    var context, args;</span><br><span class="line">    var previous &#x3D; 0;</span><br><span class="line"></span><br><span class="line">    return function() &#123;</span><br><span class="line">        var now &#x3D; +new Date();</span><br><span class="line">        context &#x3D; this;</span><br><span class="line">        args &#x3D; arguments;</span><br><span class="line">        if (now - previous &gt; wait) &#123;</span><br><span class="line">            func.apply(context, args);</span><br><span class="line">            previous &#x3D; now;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>例子依然是用讲 debounce 中的例子，如果你要使用：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">container.onmousemove &#x3D; throttle(getUserAction, 1000);</span><br></pre></td></tr></table></figure>

<p>效果演示如下：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428141501.gif" alt="使用时间戳"></p>
<p>我们可以看到：当鼠标移入的时候，事件立刻执行，每过 1s 会执行一次，如果在 4.2s 停止触发，以后不会再执行事件。</p>
<h3 id="使用定时器"><a href="#使用定时器" class="headerlink" title="使用定时器"></a>使用定时器</h3><p>接下来，我们讲讲第二种实现方式，使用定时器。</p>
<p>当触发事件的时候，我们设置一个定时器，再触发事件的时候，如果定时器存在，就不执行，直到定时器执行，然后执行函数，清空定时器，这样就可以设置下个定时器。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第二版</span><br><span class="line">function throttle(func, wait) &#123;</span><br><span class="line">    var timeout;</span><br><span class="line">    var previous &#x3D; 0;</span><br><span class="line"></span><br><span class="line">    return function() &#123;</span><br><span class="line">        context &#x3D; this;</span><br><span class="line">        args &#x3D; arguments;</span><br><span class="line">        if (!timeout) &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(function()&#123;</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">                func.apply(context, args)</span><br><span class="line">            &#125;, wait)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了让效果更加明显，我们设置 wait 的时间为 3s，效果演示如下：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210428144007.gif" alt="使用定时器"></p>
<p>我们可以看到：当鼠标移入的时候，事件不会立刻执行，晃了 3s 后终于执行了一次，此后每 3s 执行一次，当数字显示为 3 的时候，立刻移出鼠标，相当于大约 9.2s 的时候停止触发，但是依然会在第 12s 的时候执行一次事件。</p>
<p>所以比较两个方法：</p>
<ol>
<li>第一种事件会立刻执行，第二种事件会在 n 秒后第一次执行</li>
<li>第一种事件停止触发后没有办法再执行事件，第二种事件停止触发后依然会再执行一次事件</li>
</ol>
<h3 id="双剑合璧"><a href="#双剑合璧" class="headerlink" title="双剑合璧"></a>双剑合璧</h3><p>那我们想要一个什么样的呢？</p>
<p>有人就说了：我想要一个有头有尾的！就是鼠标移入能立刻执行，停止触发的时候还能再执行一次！</p>
<p>所以我们综合两者的优势，然后双剑合璧，写一版代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第三版</span><br><span class="line">function throttle(func, wait) &#123;</span><br><span class="line">    var timeout, context, args, result;</span><br><span class="line">    var previous &#x3D; 0;</span><br><span class="line"></span><br><span class="line">    var later &#x3D; function() &#123;</span><br><span class="line">        previous &#x3D; +new Date();</span><br><span class="line">        timeout &#x3D; null;</span><br><span class="line">        func.apply(context, args)</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    var throttled &#x3D; function() &#123;</span><br><span class="line">        var now &#x3D; +new Date();</span><br><span class="line">        &#x2F;&#x2F;下次触发 func 剩余的时间</span><br><span class="line">        var remaining &#x3D; wait - (now - previous);</span><br><span class="line">        context &#x3D; this;</span><br><span class="line">        args &#x3D; arguments;</span><br><span class="line">         &#x2F;&#x2F; 如果没有剩余的时间了或者你改了系统时间</span><br><span class="line">        if (remaining &lt;&#x3D; 0 || remaining &gt; wait) &#123;</span><br><span class="line">            if (timeout) &#123;</span><br><span class="line">                clearTimeout(timeout);</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">            &#125;</span><br><span class="line">            previous &#x3D; now;</span><br><span class="line">            func.apply(context, args);</span><br><span class="line">        &#125; else if (!timeout) &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(later, remaining);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    return throttled;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>效果演示如下：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514224321.gif" alt="throttle3"></p>
<p>我们可以看到：鼠标移入，事件立刻执行，晃了 3s，事件再一次执行，当数字变成 3 的时候，也就是 6s 后，我们立刻移出鼠标，停止触发事件，9s 的时候，依然会再执行一次事件。</p>
<h3 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h3><p>但是我有时也希望无头有尾，或者有头无尾，这个咋办？</p>
<p>那我们设置个 options 作为第三个参数，然后根据传的值判断到底哪种效果，我们约定:</p>
<p>leading：false 表示禁用第一次执行<br>trailing: false 表示禁用停止触发的回调</p>
<p>我们来改一下代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第四版</span><br><span class="line">function throttle(func, wait, options) &#123;</span><br><span class="line">    var timeout, context, args, result;</span><br><span class="line">    var previous &#x3D; 0;</span><br><span class="line">    if (!options) options &#x3D; &#123;&#125;;</span><br><span class="line"></span><br><span class="line">    var later &#x3D; function() &#123;</span><br><span class="line">        previous &#x3D; options.leading &#x3D;&#x3D;&#x3D; false ? 0 : new Date().getTime();</span><br><span class="line">        timeout &#x3D; null;</span><br><span class="line">        func.apply(context, args);</span><br><span class="line">        if (!timeout) context &#x3D; args &#x3D; null;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    var throttled &#x3D; function() &#123;</span><br><span class="line">        var now &#x3D; new Date().getTime();</span><br><span class="line">        if (!previous &amp;&amp; options.leading &#x3D;&#x3D;&#x3D; false) previous &#x3D; now;</span><br><span class="line">        var remaining &#x3D; wait - (now - previous);</span><br><span class="line">        context &#x3D; this;</span><br><span class="line">        args &#x3D; arguments;</span><br><span class="line">        if (remaining &lt;&#x3D; 0 || remaining &gt; wait) &#123;</span><br><span class="line">            if (timeout) &#123;</span><br><span class="line">                clearTimeout(timeout);</span><br><span class="line">                timeout &#x3D; null;</span><br><span class="line">            &#125;</span><br><span class="line">            previous &#x3D; now;</span><br><span class="line">            func.apply(context, args);</span><br><span class="line">            if (!timeout) context &#x3D; args &#x3D; null;</span><br><span class="line">        &#125; else if (!timeout &amp;&amp; options.trailing !&#x3D;&#x3D; false) &#123;</span><br><span class="line">            timeout &#x3D; setTimeout(later, remaining);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    return throttled;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="取消-1"><a href="#取消-1" class="headerlink" title="取消"></a>取消</h3><p>在 debounce 的实现中，我们加了一个 cancel 方法，throttle 我们也加个 cancel 方法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第五版 非完整代码，完整代码请查看最后的演示代码链接</span><br><span class="line">...</span><br><span class="line">throttled.cancel &#x3D; function() &#123;</span><br><span class="line">    clearTimeout(timeout);</span><br><span class="line">    previous &#x3D; 0;</span><br><span class="line">    timeout &#x3D; null;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<h3 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h3><p>我们要注意 underscore 的实现中有这样一个问题：</p>
<p>那就是 <code>leading：false</code> 和 <code>trailing: false</code> 不能同时设置。</p>
<p>如果同时设置的话，比如当你将鼠标移出的时候，因为 trailing 设置为 false，停止触发的时候不会设置定时器，所以只要再过了设置的时间，再移入的话，就会立刻执行，就违反了 leading: false，bug 就出来了，所以，这个 throttle 只有三种用法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">container.onmousemove &#x3D; throttle(getUserAction, 1000);</span><br><span class="line">container.onmousemove &#x3D; throttle(getUserAction, 1000, &#123;</span><br><span class="line">    leading: false</span><br><span class="line">&#125;);</span><br><span class="line">container.onmousemove &#x3D; throttle(getUserAction, 1000, &#123;</span><br><span class="line">    trailing: false</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>至此我们已经完整实现了一个 underscore 中的 throttle 函数，恭喜，撒花！</p>
<h3 id="面试版代码-1"><a href="#面试版代码-1" class="headerlink" title="面试版代码"></a>面试版代码</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// func是用户传入需要防抖的函数</span></span><br><span class="line"><span class="comment">// wait是等待时间</span></span><br><span class="line"><span class="keyword">const</span> throttle = <span class="function"><span class="keyword">function</span> (<span class="params">func, wait = <span class="number">50</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 上一次执行该函数的时间</span></span><br><span class="line">    <span class="keyword">let</span> lastTime = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params">...args</span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 当前时间</span></span><br><span class="line">        <span class="keyword">let</span> now = +<span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">        <span class="comment">// 将当前时间和上一次执行函数时间对比</span></span><br><span class="line">        <span class="comment">// 如果差值大于设置的等待时间就执行函数</span></span><br><span class="line">        <span class="keyword">if</span> (now - lastTime &gt; wait) &#123;</span><br><span class="line">            lastTime = now;</span><br><span class="line">            func.apply(<span class="built_in">this</span>, args)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用方法：定时器</span></span><br><span class="line"><span class="built_in">setInterval</span>(</span><br><span class="line">    throttle(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="number">1</span>)</span><br><span class="line">    &#125;, <span class="number">500</span>),</span><br><span class="line">    <span class="number">1</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<p><strong>适用场景：</strong></p>
<blockquote>
<ul>
<li>拖拽场景：固定时间内只执行一次，防止超高频次触发位置变动</li>
<li>缩放场景：监控浏览器<code>resize</code></li>
<li>动画场景：避免短时间内多次触发动画引起性能问题</li>
</ul>
</blockquote>
<h2 id="5-浅拷贝和深拷贝"><a href="#5-浅拷贝和深拷贝" class="headerlink" title="5.浅拷贝和深拷贝"></a>5.浅拷贝和深拷贝</h2><p><strong>深拷贝和浅拷贝都是针对的引用类型，JS中的变量类型分为值类型（基本类型）和引用类型；对值类型进行复制操作会对值进行一份拷贝，而对引用类型赋值，则会进行地址的拷贝，最终两个变量指向同一份数据。</strong>对于引用类型，会导致a b指向同一份数据，此时如果对其中一个进行修改，就会影响到另外一个，有时候这可能不是我们想要的结果，如果对这种现象不清楚的话，还可能造成不必要的bug。</p>
<p>那么如何切断a和b之间的关系呢，可以拷贝一份a的数据，根据拷贝的层级不同可以分为浅拷贝和深拷贝，浅拷贝就是只进行一层拷贝，深拷贝就是无限层级拷贝<strong>假设B复制了A，当修改A时，看B是否会发生变化，如果B也跟着变了，说明这是浅拷贝，拿人手短，如果B没变，那就是深拷贝，自食其力。</strong></p>
<h3 id="浅拷贝"><a href="#浅拷贝" class="headerlink" title="浅拷贝"></a>浅拷贝</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">arr.slice();</span><br><span class="line">arr.concat();</span><br></pre></td></tr></table></figure>

<h3 id="深拷贝"><a href="#深拷贝" class="headerlink" title="深拷贝"></a>深拷贝</h3><h4 id="简单版："><a href="#简单版：" class="headerlink" title="简单版："></a>简单版：</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">JSON.parse(JSON.stringify(obj))</span><br></pre></td></tr></table></figure>

<h5 id="局限性："><a href="#局限性：" class="headerlink" title="局限性："></a>局限性：</h5><ul>
<li>他无法实现对函数 、RegExp等特殊对象的克隆</li>
<li>会抛弃对象的constructor,所有的构造函数会指向Object</li>
<li>对象有循环引用,会报错</li>
</ul>
<h4 id="面试版-递归"><a href="#面试版-递归" class="headerlink" title="面试版:递归"></a>面试版:递归</h4><p>考虑到数组和对象</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">function deepCopy(obj) &#123;</span><br><span class="line">	let res </span><br><span class="line">	&#x2F;&#x2F; 判断是否是简单数据类型</span><br><span class="line">	if (typeof obj &#x3D;&#x3D; &quot;object&quot;) &#123;</span><br><span class="line">		&#x2F;&#x2F; 复杂数据类型</span><br><span class="line">		res &#x3D; obj.constructor &#x3D;&#x3D; Array ? [] : &#123;&#125;;</span><br><span class="line">		for (let i in obj) &#123;</span><br><span class="line">			res[i] &#x3D; typeof obj[i] &#x3D;&#x3D; &quot;object&quot; ? deepCopy(obj[i]) : obj[i]</span><br><span class="line">		&#125;</span><br><span class="line">	&#125; else &#123;</span><br><span class="line">		&#x2F;&#x2F; 简单数据类型 直接 &#x3D;&#x3D; 赋值</span><br><span class="line">		res &#x3D; obj;</span><br><span class="line">	&#125;</span><br><span class="line">	return res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="循环引用"><a href="#循环引用" class="headerlink" title="循环引用"></a>循环引用</h4><p>上述版本执行下面这样一个测试用例：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> target = &#123;</span><br><span class="line">    field1: <span class="number">1</span>,</span><br><span class="line">    field2: <span class="literal">undefined</span>,</span><br><span class="line">    field3: &#123;</span><br><span class="line">        child: <span class="string">&#x27;child&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    field4: [<span class="number">2</span>, <span class="number">4</span>, <span class="number">8</span>]</span><br><span class="line">&#125;;</span><br><span class="line">target.target = target;</span><br></pre></td></tr></table></figure>

<p>因为递归进入死循环导致栈内存溢出了。</p>
<p>原因就是上面的对象存在循环引用的情况，即对象的属性间接或直接的引用了自身的情况：</p>
<p>解决循环引用问题，我们可以额外开辟一个存储空间，来存储当前对象和拷贝对象的对应关系，当需要拷贝当前对象时，先去存储空间中找，有没有拷贝过这个对象，如果有的话直接返回，如果没有的话继续拷贝，这样就巧妙化解的循环引用的问题。</p>
<p>这个存储空间，需要可以存储 <code>key-value</code>形式的数据，且 <code>key</code>可以是一个引用类型，我们可以选择 <code>Map</code>这种数据结构：</p>
<ul>
<li>检查<code>map</code>中有无克隆过的对象</li>
<li>有 - 直接返回</li>
<li>没有 - 将当前对象作为<code>key</code>，克隆对象作为<code>value</code>进行存储</li>
<li>继续克隆</li>
</ul>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepCopy</span>(<span class="params">obj, map = <span class="keyword">new</span> <span class="built_in">Map</span>()</span>) </span>&#123;</span><br><span class="line">	<span class="keyword">let</span> res;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> obj === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">        res = obj.constructor == <span class="built_in">Array</span> ? [] : &#123;&#125;;</span><br><span class="line">        <span class="keyword">if</span> (map.get(obj)) &#123;</span><br><span class="line">            <span class="keyword">return</span> obj;</span><br><span class="line">        &#125;</span><br><span class="line">        map.set(obj, res);</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="keyword">in</span> obj) &#123;</span><br><span class="line">            res[i] = <span class="keyword">typeof</span> obj[i] == <span class="string">&quot;object&quot;</span> ? deepCopy(obj[i]) : obj[i];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 简单数据类型 直接 == 赋值</span></span><br><span class="line">		res = obj;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> res;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>可以看到，执行没有报错，且 <code>target</code>属性，变为了一个 <code>Circular</code>类型，即循环应用的意思。</p>
<p>接下来，我们可以使用， <code>WeakMap</code>提代 <code>Map</code>来使代码达到画龙点睛的作用。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepCopy</span>(<span class="params">obj, map = <span class="keyword">new</span> <span class="built_in">WeakMap</span>()</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>为什么要这样做呢？，先来看看 <code>WeakMap</code>的作用：</p>
<blockquote>
<p>WeakMap 对象是一组键/值对的集合，其中的键是弱引用的。其键必须是对象，而值可以是任意的。</p>
</blockquote>
<p>什么是弱引用呢？</p>
<blockquote>
<p>在计算机程序设计中，弱引用与强引用相对，是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用，则被认为是不可访问（或弱可访问）的，并因此可能在任何时刻被回收。</p>
</blockquote>
<p>我们默认创建一个对象：<code>const obj=&#123;&#125;</code>，就默认创建了一个强引用的对象，我们只有手动将 <code>obj=null</code>，它才会被垃圾回收机制进行回收，如果是弱引用对象，垃圾回收机制会自动帮我们回收。</p>
<p>举个例子：</p>
<p>如果我们使用 <code>Map</code>的话，那么对象间是存在强引用关系的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123; <span class="attr">name</span> : <span class="string">&#x27;ConardLi&#x27;</span>&#125;</span><br><span class="line"><span class="keyword">const</span> target = &#123;</span><br><span class="line">    obj:<span class="string">&#x27;code秘密花园&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line">obj = <span class="literal">null</span>;</span><br></pre></td></tr></table></figure>

<p>虽然我们手动将 <code>obj</code>，进行释放，然是 <code>target</code>依然对 <code>obj</code>存在强引用关系，所以这部分内存依然无法被释放。</p>
<p>再来看 <code>WeakMap</code>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> obj = &#123; <span class="attr">name</span> : <span class="string">&#x27;ConardLi&#x27;</span>&#125;</span><br><span class="line"><span class="keyword">const</span> target = <span class="keyword">new</span> <span class="built_in">WeakMap</span>();</span><br><span class="line">target.set(obj,<span class="string">&#x27;code秘密花园&#x27;</span>);</span><br><span class="line">obj = <span class="literal">null</span>;</span><br></pre></td></tr></table></figure>

<p>如果是 <code>WeakMap</code>的话， <code>target</code>和 <code>obj</code>存在的就是弱引用关系，当下一次垃圾回收机制执行时，这块内存就会被释放掉。</p>
<p>设想一下，如果我们要拷贝的对象非常庞大时，使用 <code>Map</code>会对内存造成非常大的额外消耗，而且我们需要手动清除 <code>Map</code>的属性才能释放这块内存，而 <code>WeakMap</code>会帮我们巧妙化解这个问题。</p>
<p>我也经常在某些代码中看到有人使用 <code>WeakMap</code>来解决循环引用问题，但是解释都是模棱两可的，当你不太了解 <code>WeakMap</code>的真正作用时。我建议你也不要在面试中写这样的代码，结果只能是给自己挖坑，即使是准备面试，你写的每一行代码也都是需要经过深思熟虑并且非常明白的。</p>
<p>能考虑到循环引用的问题，你已经向面试官展示了你考虑问题的全面性，如果还能用 <code>WeakMap</code>解决问题，并很明确的向面试官解释这样做的目的，那么你的代码在面试官眼里应该算是合格了。</p>
<h4 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h4><p>在上面的代码中，我们遍历数组和对象都使用了 <code>forin</code>这种方式，实际上 <code>forin</code>在遍历时效率是非常低的，对比下常见的三种循环 <code>for、while、forin</code>的执行效率：</p>
<p><img data-src="https://ask.qcloudimg.com/http-save/yehe-3713434/sf9725l2cd.jpeg?imageView2/2/w/1620" alt="img"></p>
<p> <code>while</code>的效率是最好的，所以，我们可以想办法把 <code>forin</code>遍历改变为 <code>while</code>遍历。</p>
<p>我们先使用 <code>while</code>来实现一个通用的 <code>forEach</code>遍历， <code>iteratee</code>是遍历的回掉函数，他可以接收每次遍历的 <code>value</code>和 <code>index</code>两个参数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">forEach</span>(<span class="params">array, iteratee</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> index = -<span class="number">1</span>;</span><br><span class="line">    <span class="keyword">const</span> length = array.length;</span><br><span class="line">    <span class="keyword">while</span> (++index &lt; length) &#123;</span><br><span class="line">        iteratee(array[index], index);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> array;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面对我们的 <code>cloen</code>函数进行改写：当遍历数组时，直接使用 <code>forEach</code>进行遍历，当遍历对象时，使用 <code>Object.keys</code>取出所有的 <code>key</code>进行遍历，然后在遍历时把 <code>forEach</code>会调函数的 <code>value</code>当作 <code>key</code>使用：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepCopy</span>(<span class="params">obj, map = <span class="keyword">new</span> <span class="built_in">WeakMap</span>()</span>) </span>&#123;</span><br><span class="line">	<span class="keyword">let</span> res;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> obj === <span class="string">&#x27;object&#x27;</span>) &#123;</span><br><span class="line">        res = obj.constructor == <span class="built_in">Array</span> ? [] : &#123;&#125;;</span><br><span class="line">        <span class="keyword">if</span> (map.get(obj)) &#123;</span><br><span class="line">            <span class="keyword">return</span> obj;</span><br><span class="line">        &#125;</span><br><span class="line">        map.set(obj, res);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">const</span> keys = obj.constructor == <span class="built_in">Array</span> ?  <span class="literal">undefined</span> : <span class="built_in">Object</span>.keys(target);</span><br><span class="line">        forEach(keys || target, <span class="function">(<span class="params">value, key</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (keys) &#123;</span><br><span class="line">                key = value;</span><br><span class="line">            &#125;</span><br><span class="line">            res[key] = deepCopy(obj[key], map);</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 简单数据类型 直接 == 赋值</span></span><br><span class="line">		res = obj;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> res;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h4 id="其他数据类型"><a href="#其他数据类型" class="headerlink" title="其他数据类型"></a>其他数据类型</h4><p>在上面的代码中，我们其实只考虑了普通的 <code>object</code>和 <code>array</code>两种数据类型，实际上所有的引用类型远远不止这两个，还有很多，下面我们先尝试获取对象准确的类型。</p>
<h5 id="合理的判断引用类型"><a href="#合理的判断引用类型" class="headerlink" title="合理的判断引用类型"></a>合理的判断引用类型</h5><p>首先，判断是否为引用类型，我们还需要考虑 <code>function</code>和 <code>null</code>两种特殊的数据类型：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">isObject</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> type = <span class="keyword">typeof</span> obj;</span><br><span class="line">    <span class="keyword">return</span> obj !== <span class="literal">null</span> &amp;&amp; (type === <span class="string">&#x27;object&#x27;</span> || type === <span class="string">&#x27;function&#x27;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (!isObject(obj)) &#123;</span><br><span class="line">        <span class="keyword">return</span> obj;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br></pre></td></tr></table></figure>

<h5 id="获取数据类型"><a href="#获取数据类型" class="headerlink" title="获取数据类型"></a>获取数据类型</h5><p>我们可以使用 <code>toString</code>来获取准确的引用类型：</p>
<blockquote>
<p>每一个引用类型都有 <code>toString</code>方法，默认情况下， <code>toString()</code>方法被每个 <code>Object</code>对象继承。如果此方法在自定义对象中未被覆盖，t <code>oString()</code>返回 <code>&quot;[object type]&quot;</code>，其中type是对象的类型。</p>
</blockquote>
<p>注意，上面提到了如果此方法在自定义对象中未被覆盖， <code>toString</code>才会达到预想的效果，事实上，大部分引用类型比如 <code>Array、Date、RegExp</code>等都重写了 <code>toString</code>方法。</p>
<p>我们可以直接调用 <code>Object</code>原型上未被覆盖的 <code>toString()</code>方法，使用 <code>call</code>来改变 <code>this</code>指向来达到我们想要的效果。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getType</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Object</span>.prototype.toString.call(target);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img data-src="https://ask.qcloudimg.com/http-save/yehe-3713434/nb0dwyns7q.jpeg?imageView2/2/w/1620" alt="img"></p>
<p>下面我们抽离出一些常用的数据类型以便后面使用：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> mapTag = <span class="string">&#x27;[object Map]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> setTag = <span class="string">&#x27;[object Set]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> arrayTag = <span class="string">&#x27;[object Array]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> objectTag = <span class="string">&#x27;[object Object]&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> boolTag = <span class="string">&#x27;[object Boolean]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> dateTag = <span class="string">&#x27;[object Date]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> errorTag = <span class="string">&#x27;[object Error]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> numberTag = <span class="string">&#x27;[object Number]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> regexpTag = <span class="string">&#x27;[object RegExp]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> stringTag = <span class="string">&#x27;[object String]&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> symbolTag = <span class="string">&#x27;[object Symbol]&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>在上面的集中类型中，我们简单将他们分为两类：</p>
<ul>
<li>可以继续遍历的类型</li>
<li>不可以继续遍历的类型</li>
</ul>
<p>我们分别为它们做不同的拷贝。</p>
<h5 id="可继续遍历的类型"><a href="#可继续遍历的类型" class="headerlink" title="可继续遍历的类型"></a>可继续遍历的类型</h5><p>上面我们已经考虑的 <code>object</code>、 <code>array</code>都属于可以继续遍历的类型，因为它们内存都还可以存储其他数据类型的数据，另外还有 <code>Map</code>， <code>Set</code>等都是可以继续遍历的类型，这里我们只考虑这四种，如果你有兴趣可以继续探索其他类型。</p>
<p>有序这几种类型还需要继续进行递归，我们首先需要获取它们的初始化数据，例如上面的 <code>[]</code>和 <code>&#123;&#125;</code>，我们可以通过拿到 <code>constructor</code>的方式来通用的获取。</p>
<p>例如：<code>consttarget=&#123;&#125;</code>就是 <code>consttarget=newObject()</code>的语法糖。另外这种方法还有一个好处：因为我们还使用了原对象的构造方法，所以它可以保留对象原型上的数据，如果直接使用普通的 <code>&#123;&#125;</code>，那么原型必然是丢失了的。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getInit</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> Ctor = target.constructor;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> Ctor();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>下面，我们改写 <code>clone</code>函数，对可继续遍历的数据类型进行处理：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">deepCopy</span>(<span class="params">obj, map = <span class="keyword">new</span> <span class="built_in">WeakMap</span>()</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 克隆原始类型</span></span><br><span class="line">    <span class="keyword">if</span> (!isObject(obj)) &#123;</span><br><span class="line">        <span class="keyword">return</span> obj;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 初始化</span></span><br><span class="line">    <span class="keyword">const</span> type = getType(obj);</span><br><span class="line">    <span class="keyword">let</span> res;</span><br><span class="line">    <span class="keyword">if</span> (deepTag.includes(type)) &#123;</span><br><span class="line">        res = getInit(obj, type);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 防止循环引用</span></span><br><span class="line">    <span class="keyword">if</span> (map.get(obj)) &#123;</span><br><span class="line">        <span class="keyword">return</span> obj;</span><br><span class="line">    &#125;</span><br><span class="line">    map.set(obj, res);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 克隆set</span></span><br><span class="line">    <span class="keyword">if</span> (type === setTag) &#123;</span><br><span class="line">        ob.forEach(<span class="function"><span class="params">value</span> =&gt;</span> &#123;</span><br><span class="line">            res.add(deepCopy(value));</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 克隆map</span></span><br><span class="line">    <span class="keyword">if</span> (type === mapTag) &#123;</span><br><span class="line">        target.forEach(<span class="function">(<span class="params">value, key</span>) =&gt;</span> &#123;</span><br><span class="line">            res.set(key, deepCopy(value));</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 克隆对象和数组</span></span><br><span class="line">    <span class="keyword">const</span> keys = type === arrayTag ? <span class="literal">undefined</span> : <span class="built_in">Object</span>.keys(obj);</span><br><span class="line">    forEach(keys || target, <span class="function">(<span class="params">value, key</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (keys) &#123;</span><br><span class="line">            key = value;</span><br><span class="line">        &#125;</span><br><span class="line">        res[key] = deepCopy(obj[key], map);</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们执行clone5.test.js对下面的测试用例进行测试：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> target = &#123;</span><br><span class="line">    field1: <span class="number">1</span>,</span><br><span class="line">    field2: <span class="literal">undefined</span>,</span><br><span class="line">    field3: &#123;</span><br><span class="line">        child: <span class="string">&#x27;child&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    field4: [<span class="number">2</span>, <span class="number">4</span>, <span class="number">8</span>],</span><br><span class="line">    empty: <span class="literal">null</span>,</span><br><span class="line">    map,</span><br><span class="line">    set,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>执行结果：</p>
<p><img data-src="https://ask.qcloudimg.com/http-save/yehe-3713434/fh73smmild.jpeg?imageView2/2/w/1620" alt="img"></p>
<p>没有问题，继续处理其他类型：</p>
<h5 id="不可继续遍历的类型"><a href="#不可继续遍历的类型" class="headerlink" title="不可继续遍历的类型"></a>不可继续遍历的类型</h5><p>其他剩余的类型我们把它们统一归类成不可处理的数据类型，我们依次进行处理：</p>
<p><code>Bool</code>、 <code>Number</code>、 <code>String</code>、 <code>String</code>、 <code>Date</code>、 <code>Error</code>这几种类型我们都可以直接用构造函数和原始数据创建一个新对象：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cloneOtherType</span>(<span class="params">obj, type</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> Ctor = obj.constructor;</span><br><span class="line">    <span class="keyword">switch</span> (type) &#123;</span><br><span class="line">        <span class="keyword">case</span> boolTag:</span><br><span class="line">        <span class="keyword">case</span> numberTag:</span><br><span class="line">        <span class="keyword">case</span> stringTag:</span><br><span class="line">        <span class="keyword">case</span> errorTag:</span><br><span class="line">        <span class="keyword">case</span> dateTag:</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Ctor(obj);</span><br><span class="line">        <span class="keyword">case</span> regexpTag:</span><br><span class="line">            <span class="keyword">return</span> cloneReg(obj);</span><br><span class="line">        <span class="keyword">case</span> symbolTag:</span><br><span class="line">            <span class="keyword">return</span> cloneSymbol(obj);</span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>克隆 <code>Symbol</code>类型：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cloneSymbol</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Object</span>(<span class="built_in">Symbol</span>.prototype.valueOf.call(obj));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">克隆正则：</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cloneReg</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> reFlags = <span class="regexp">/\w*$/</span>;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">new</span> obj.constructor(obj.source, reFlags.exec(obj));</span><br><span class="line">    res.lastIndex = obj.lastIndex;</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>实际上还有很多数据类型我这里没有写到，有兴趣的话可以继续探索实现一下。</p>
<p>能写到这里，面试官已经看到了你考虑问题的严谨性，你对变量和类型的理解，对 <code>JS API</code>的熟练程度，相信面试官已经开始对你刮目相看了。</p>
<h4 id="克隆函数"><a href="#克隆函数" class="headerlink" title="克隆函数"></a>克隆函数</h4><p>最后，我把克隆函数单独拎出来了，实际上克隆函数是没有实际应用场景的，两个对象使用一个在内存中处于同一个地址的函数也是没有任何问题的，我特意看了下 <code>lodash</code>对函数的处理：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isFunc = <span class="keyword">typeof</span> value == <span class="string">&#x27;function&#x27;</span></span><br><span class="line"> <span class="keyword">if</span> (isFunc || !cloneableTags[tag]) &#123;</span><br><span class="line">        <span class="keyword">return</span> object ? value : &#123;&#125;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>可见这里如果发现是函数的话就会直接返回了，没有做特殊的处理，但是我发现不少面试官还是热衷于问这个问题的，而且据我了解能写出来的少之又少。。。</p>
<p>实际上这个方法并没有什么难度，主要就是考察你对基础的掌握扎实不扎实。</p>
<p>首先，我们可以通过 <code>prototype</code>来区分下箭头函数和普通函数，箭头函数是没有 <code>prototype</code>的。</p>
<p>我们可以直接使用 <code>eval</code>和函数字符串来重新生成一个箭头函数，注意这种方法是不适用于普通函数的。</p>
<p>我们可以使用正则来处理普通函数：</p>
<p>分别使用正则取出函数体和函数参数，然后使用 <code>newFunction([arg1[,arg2[,...argN]],]functionBody)</code>构造函数重新构造一个新的函数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">cloneFunction</span>(<span class="params">func</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> bodyReg = <span class="regexp">/(?&lt;=&#123;)(.|\n)+(?=&#125;)/m</span>;</span><br><span class="line">    <span class="keyword">const</span> paramReg = <span class="regexp">/(?&lt;=\().+(?=\)\s+&#123;)/</span>;</span><br><span class="line">    <span class="keyword">const</span> funcString = func.toString();</span><br><span class="line">    <span class="keyword">if</span> (func.prototype) &#123;</span><br><span class="line">        <span class="comment">// console.log(&#x27;普通函数&#x27;);</span></span><br><span class="line">        <span class="keyword">const</span> param = paramReg.exec(funcString);</span><br><span class="line">        <span class="keyword">const</span> body = bodyReg.exec(funcString);</span><br><span class="line">        <span class="keyword">if</span> (body) &#123;</span><br><span class="line">            <span class="comment">// console.log(&#x27;匹配到函数体：&#x27;, body[0]);</span></span><br><span class="line">            <span class="keyword">if</span> (param) &#123;</span><br><span class="line">                <span class="keyword">const</span> paramArr = param[<span class="number">0</span>].split(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">                <span class="built_in">console</span>.log(<span class="string">&#x27;匹配到参数：&#x27;</span>, paramArr);</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Function</span>(...paramArr, body[<span class="number">0</span>]);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Function</span>(body[<span class="number">0</span>]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">eval</span>(funcString);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>最后，我们再来执行clone6.test.js对下面的测试用例进行测试：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="built_in">Map</span>();</span><br><span class="line">map.set(<span class="string">&#x27;key&#x27;</span>, <span class="string">&#x27;value&#x27;</span>);</span><br><span class="line">map.set(<span class="string">&#x27;ConardLi&#x27;</span>, <span class="string">&#x27;code秘密花园&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> set = <span class="keyword">new</span> <span class="built_in">Set</span>();</span><br><span class="line">set.add(<span class="string">&#x27;ConardLi&#x27;</span>);</span><br><span class="line">set.add(<span class="string">&#x27;code秘密花园&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> target = &#123;</span><br><span class="line">    field1: <span class="number">1</span>,</span><br><span class="line">    field2: <span class="literal">undefined</span>,</span><br><span class="line">    field3: &#123;</span><br><span class="line">        child: <span class="string">&#x27;child&#x27;</span></span><br><span class="line">    &#125;,</span><br><span class="line">    field4: [<span class="number">2</span>, <span class="number">4</span>, <span class="number">8</span>],</span><br><span class="line">    empty: <span class="literal">null</span>,</span><br><span class="line">    map,</span><br><span class="line">    set,</span><br><span class="line">    bool: <span class="keyword">new</span> <span class="built_in">Boolean</span>(<span class="literal">true</span>),</span><br><span class="line">    num: <span class="keyword">new</span> <span class="built_in">Number</span>(<span class="number">2</span>),</span><br><span class="line">    str: <span class="keyword">new</span> <span class="built_in">String</span>(<span class="number">2</span>),</span><br><span class="line">    symbol: <span class="built_in">Object</span>(<span class="built_in">Symbol</span>(<span class="number">1</span>)),</span><br><span class="line">    date: <span class="keyword">new</span> <span class="built_in">Date</span>(),</span><br><span class="line">    reg: <span class="regexp">/\d+/</span>,</span><br><span class="line">    error: <span class="keyword">new</span> <span class="built_in">Error</span>(),</span><br><span class="line">    func1: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">&#x27;code秘密花园&#x27;</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    func2: <span class="function"><span class="keyword">function</span> (<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> a + b;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>执行结果：</p>
<p><img data-src="https://ask.qcloudimg.com/http-save/yehe-3713434/xtb15ikjai.jpeg?imageView2/2/w/1620" alt="img"></p>
<h4 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h4><p>为了更好的阅读，我们用一张图来展示上面所有的代码：</p>
<p><img data-src="https://ask.qcloudimg.com/http-save/yehe-3713434/im20c61hq4.jpeg?imageView2/2/w/1620" alt="img"></p>
<p>完整代码：<a target="_blank" rel="noopener" href="https://github.com/ConardLi/ConardLi.github.io/blob/master/demo/deepClone/src/clone_6.js">https://github.com/ConardLi/ConardLi.github.io/blob/master/demo/deepClone/src/clone_6.js</a></p>
<p>可见，一个小小的深拷贝还是隐藏了很多的知识点的。</p>
<p>千万不要以最低的要求来要求自己，如果你只是为了应付面试中的一个题目，那么你可能只会去准备上面最简陋的深拷贝的方法。</p>
<p>但是面试官考察你的目的是全方位的考察你的思维能力，如果你写出上面的代码，可以体现你多方位的能力：</p>
<ul>
<li>基本实现<ul>
<li>递归能力</li>
</ul>
</li>
<li>循环引用<ul>
<li>考虑问题的全面性</li>
<li>理解weakmap的真正意义</li>
</ul>
</li>
<li>多种类型<ul>
<li>考虑问题的严谨性</li>
<li>创建各种引用类型的方法，JS API的熟练程度</li>
<li>准确的判断数据类型，对数据类型的理解程度</li>
</ul>
</li>
<li>通用遍历：<ul>
<li>写代码可以考虑性能优化</li>
<li>了解集中遍历的效率</li>
<li>代码抽象能力</li>
</ul>
</li>
<li>拷贝函数：<ul>
<li>箭头函数和普通函数的区别</li>
<li>正则表达式熟练程度</li>
</ul>
</li>
</ul>
<p>看吧，一个小小的深拷贝能考察你这么多的能力，如果面试官看到这样的代码，怎么能够不惊艳呢？</p>
<p>其实面试官出的所有题目你都可以用这样的思路去考虑。不要为了应付面试而去背一些代码，这样在有经验的面试官面前会都会暴露出来。你写的每一段代码都要经过深思熟虑，为什么要这样用，还能怎么优化…这样才能给面试官展现一个最好的你。</p>
<h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><ul>
<li>WeakMap</li>
<li>lodash</li>
</ul>
<h2 id="6-数组去重、扁平、最值"><a href="#6-数组去重、扁平、最值" class="headerlink" title="6.数组去重、扁平、最值"></a>6.数组去重、扁平、最值</h2><h3 id="去重"><a href="#去重" class="headerlink" title="去重"></a>去重</h3><h4 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function unique (array) &#123;</span><br><span class="line">    let container &#x3D; &#123;&#125;;</span><br><span class="line">    return array.filter((item, index) &#x3D;&gt;  container.hasOwnProperty(item) ? false : (container[item] &#x3D; true));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="indexOf-filter"><a href="#indexOf-filter" class="headerlink" title="indexOf + filter"></a>indexOf + filter</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">function unique (arr) &#123;</span><br><span class="line">	return arr.filter((e,i) &#x3D;&gt; arr.indexOf(e) &#x3D;&#x3D;&#x3D; i);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">function unique (arr) &#123;</span><br><span class="line">	return  [...new Set(arr)];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="扁平"><a href="#扁平" class="headerlink" title="扁平"></a>扁平</h3><blockquote>
<p><a target="_blank" rel="noopener" href="https://github.com/NieZhuZhu/Blog">https://github.com/NieZhuZhu/Blog</a>)</p>
</blockquote>
<h4 id="一段代码总结-Array-prototype-flat-特性"><a href="#一段代码总结-Array-prototype-flat-特性" class="headerlink" title="一段代码总结 Array.prototype.flat() 特性"></a>一段代码总结 <code>Array.prototype.flat()</code> 特性</h4><blockquote>
<p>注：数组拍平方法 <code>Array.prototype.flat()</code> 也叫数组扁平化、数组拉平、数组降维。 本文统一叫：数组拍平</p>
</blockquote>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">const animals &#x3D; [&quot;🐷&quot;, [&quot;🐶&quot;, &quot;🐂&quot;], [&quot;🐎&quot;, [&quot;🐑&quot;, [&quot;🐲&quot;]], &quot;🐛&quot;]];</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 不传参数时，默认“拉平”一层</span><br><span class="line">animals.flat();</span><br><span class="line">&#x2F;&#x2F; [&quot;🐷&quot;, &quot;🐶&quot;, &quot;🐂&quot;, &quot;🐎&quot;, [&quot;🐑&quot;, [&quot;🐲&quot;]], &quot;🐛&quot;]</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 传入一个整数参数，整数即“拉平”的层数</span><br><span class="line">animals.flat(2);</span><br><span class="line">&#x2F;&#x2F; [&quot;🐷&quot;, &quot;🐶&quot;, &quot;🐂&quot;, &quot;🐎&quot;, &quot;🐑&quot;, [&quot;🐲&quot;], &quot;🐛&quot;]</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; Infinity 关键字作为参数时，无论多少层嵌套，都会转为一维数组</span><br><span class="line">animals.flat(Infinity);</span><br><span class="line">&#x2F;&#x2F; [&quot;🐷&quot;, &quot;🐶&quot;, &quot;🐂&quot;, &quot;🐎&quot;, &quot;🐑&quot;, &quot;🐲&quot;, &quot;🐛&quot;]</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 传入 &lt;&#x3D;0 的整数将返回原数组，不“拉平”</span><br><span class="line">animals.flat(0);</span><br><span class="line">animals.flat(-10);</span><br><span class="line">&#x2F;&#x2F; [&quot;🐷&quot;, [&quot;🐶&quot;, &quot;🐂&quot;], [&quot;🐎&quot;, [&quot;🐑&quot;, [&quot;🐲&quot;]], &quot;🐛&quot;]];</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 如果原数组有空位，flat()方法会跳过空位。</span><br><span class="line">[&quot;🐷&quot;, &quot;🐶&quot;, &quot;🐂&quot;, &quot;🐎&quot;,,].flat();</span><br><span class="line">&#x2F;&#x2F; [&quot;🐷&quot;, &quot;🐶&quot;, &quot;🐂&quot;, &quot;🐎&quot;]</span><br></pre></td></tr></table></figure>

<h4 id="Array-prototype-flat-特性总结"><a href="#Array-prototype-flat-特性总结" class="headerlink" title="Array.prototype.flat() 特性总结"></a><code>Array.prototype.flat()</code> 特性总结</h4><ul>
<li><code>Array.prototype.flat()</code> 用于将嵌套的数组“拉平”，变成一维的数组。该方法返回一个新数组，对原数据没有影响。</li>
<li>不传参数时，默认“拉平”一层，可以传入一个整数，表示想要“拉平”的层数。</li>
<li>传入 <code>&lt;=0</code> 的整数将返回原数组，不“拉平”</li>
<li><code>Infinity</code> 关键字作为参数时，无论多少层嵌套，都会转为一维数组</li>
<li>如果原数组有空位，<code>Array.prototype.flat()</code> 会跳过空位。</li>
</ul>
<h4 id="面试官-N-连问"><a href="#面试官-N-连问" class="headerlink" title="面试官 N 连问"></a>面试官 N 连问</h4><h5 id="第一问：实现一个简单的数组拍平-flat-函数"><a href="#第一问：实现一个简单的数组拍平-flat-函数" class="headerlink" title="第一问：实现一个简单的数组拍平 flat 函数"></a>第一问：实现一个简单的数组拍平 <code>flat</code> 函数</h5><p>首先，我们将花一点篇幅来探讨如何实现一个简单的数组拍平 <code>flat</code> 函数，详细介绍多种实现的方案，然后再尝试接住面试官的连环追问。</p>
<h6 id="实现思路"><a href="#实现思路" class="headerlink" title="实现思路"></a>实现思路</h6><p>如何实现呢，思路非常简单：实现一个有数组拍平功能的 <code>flat</code> 函数，<strong>我们要做的就是在数组中找到是数组类型的元素，然后将他们展开</strong>。这就是实现数组拍平 <code>flat</code> 方法的关键思路。</p>
<p>有了思路，我们就需要解决实现这个思路需要克服的困难：</p>
<ul>
<li><strong>第一个要解决的就是遍历数组的每一个元素；</strong></li>
<li><strong>第二个要解决的就是判断元素是否是数组；</strong></li>
<li><strong>第三个要解决的就是将数组的元素展开一层；</strong></li>
</ul>
<h6 id="遍历数组的方案"><a href="#遍历数组的方案" class="headerlink" title="遍历数组的方案"></a>遍历数组的方案</h6><p>遍历数组并取得数组元素的方法非常之多，<strong>包括且不限于下面几种</strong>：</p>
<ul>
<li><code>for 循环</code></li>
<li><code>for...of</code></li>
<li><code>for...in</code></li>
<li><code>forEach()</code></li>
<li><code>entries()</code></li>
<li><code>keys()</code></li>
<li><code>values()</code></li>
<li><code>reduce()</code></li>
<li><code>map()</code></li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line">&#x2F;&#x2F; 遍历数组的方法有太多，本文只枚举常用的几种</span><br><span class="line">&#x2F;&#x2F; for 循环</span><br><span class="line">for (let i &#x3D; 0; i &lt; arr.length; i++) &#123;</span><br><span class="line">	console.log(arr[i]);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; for...of</span><br><span class="line">for (let value of arr) &#123;</span><br><span class="line">	console.log(value);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; for...in</span><br><span class="line">for (let i in arr) &#123;</span><br><span class="line">	console.log(arr[i]);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; forEach 循环</span><br><span class="line">arr.forEach(value &#x3D;&gt; &#123;</span><br><span class="line">	console.log(value);</span><br><span class="line">&#125;);</span><br><span class="line">&#x2F;&#x2F; entries（）</span><br><span class="line">for (let [index, value] of arr.entries()) &#123;</span><br><span class="line">	console.log(value);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; keys()</span><br><span class="line">for (let index of arr.keys()) &#123;</span><br><span class="line">	console.log(arr[index]);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; values()</span><br><span class="line">for (let value of arr.values()) &#123;</span><br><span class="line">	console.log(value);</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; reduce()</span><br><span class="line">arr.reduce((pre, cur) &#x3D;&gt; &#123;</span><br><span class="line">	console.log(cur);</span><br><span class="line">&#125;, []);</span><br><span class="line">&#x2F;&#x2F; map()</span><br><span class="line">arr.map(value &#x3D;&gt; console.log(value));</span><br></pre></td></tr></table></figure>

<p>只要是能够遍历数组取到数组中每一个元素的方法，都是一种可行的解决方案。</p>
<h6 id="判断元素是数组的方案"><a href="#判断元素是数组的方案" class="headerlink" title="判断元素是数组的方案"></a>判断元素是数组的方案</h6><ul>
<li><code>instanceof</code></li>
<li><code>constructor</code></li>
<li><code>Object.prototype.toString</code></li>
<li><code>isArray</code></li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line">arr instanceof Array;</span><br><span class="line">&#x2F;&#x2F; true</span><br><span class="line">arr.constructor &#x3D;&#x3D;&#x3D; Array;</span><br><span class="line">&#x2F;&#x2F; true</span><br><span class="line">Object.prototype.toString.call(arr) &#x3D;&#x3D;&#x3D; &#39;[object Array]&#39;;</span><br><span class="line">&#x2F;&#x2F; true</span><br><span class="line">Array.isArray(arr);</span><br><span class="line">&#x2F;&#x2F; true</span><br></pre></td></tr></table></figure>

<p><strong>说明</strong>：</p>
<ul>
<li><p><code>instanceof</code> 操作符是假定只有一种全局环境，如果网页中包含多个框架，多个全局环境，如果你从一个框架向另一个框架传入一个数组，那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。（所以在这种情况下会不准确）</p>
</li>
<li><p><code>typeof</code> 操作符对数组取类型将返回 <code>object</code></p>
</li>
<li><p>因为<code> constructor</code> 可以被重写，所以不能确保一定是数组。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">const str &#x3D; &#39;abc&#39;;</span><br><span class="line">str.constructor &#x3D; Array;</span><br><span class="line">str.constructor &#x3D;&#x3D;&#x3D; Array;</span><br><span class="line">&#x2F;&#x2F; true</span><br></pre></td></tr></table></figure>

</li>
</ul>
<h6 id="将数组的元素展开一层的方案"><a href="#将数组的元素展开一层的方案" class="headerlink" title="将数组的元素展开一层的方案"></a>将数组的元素展开一层的方案</h6><ul>
<li><h6 id="扩展运算符-concat"><a href="#扩展运算符-concat" class="headerlink" title="扩展运算符 +concat"></a>扩展运算符 +concat</h6></li>
<li><code>concat()</code> 方法用于合并两个或多个数组，在拼接的过程中加上扩展运算符会展开一层数组。详细见下面的代码。</li>
<li><code>conca</code>t +<code> apply</code></li>
</ul>
<p>主要是利用 <code>apply</code> 在绑定作用域时，传入的第二个参数是一个数组或者类数组对象，其中的数组元素将作为单独的参数传给 <code>func</code> 函数。也就是在调用 <code>apply</code> 函数的过程中，会将传入的数组一个一个的传入到要执行的函数中，也就是相当对数组进行了一层的展开。</p>
<ul>
<li><code>toString</code> + <code>split</code></li>
</ul>
<p>不推荐使用 <code>toString</code> + <code>split</code> 方法，因为操作字符串是和危险的事情，在<a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000021230185">上一文章</a>中我做了一个操作字符串的案例还被许多小伙伴们批评了。如果数组中的元素所有都是数字的话，<code>toString</code> +<code> split</code> 是可行的，并且是一步搞定。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line">&#x2F;&#x2F; 扩展运算符 + concat</span><br><span class="line">[].concat(...arr)</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; concat + apply</span><br><span class="line">[].concat.apply([], arr);</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; toString  + split</span><br><span class="line">const arr2 &#x3D;[1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]]]</span><br><span class="line">arr2.toString().split(&#39;,&#39;).map(v&#x3D;&gt;parseInt(v))</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3]</span><br></pre></td></tr></table></figure>

<p>总结完要解决的三大困难，那我们就可以非常轻松的实现一版数组拍平 <code>flat</code> 函数了。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line">&#x2F;&#x2F; concat + 递归</span><br><span class="line">function flat(arr) &#123;</span><br><span class="line">  let arrResult &#x3D; [];</span><br><span class="line">  arr.forEach(item &#x3D;&gt; &#123;</span><br><span class="line">    if (Array.isArray(item)) &#123;</span><br><span class="line">      arrResult &#x3D; arrResult.concat(arguments.callee(item));   &#x2F;&#x2F; 递归</span><br><span class="line">      &#x2F;&#x2F; 或者用扩展运算符</span><br><span class="line">      &#x2F;&#x2F; arrResult.push(...arguments.callee(item));</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      arrResult.push(item);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  return arrResult;</span><br><span class="line">&#125;</span><br><span class="line">flat(arr)</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<p>到这里，恭喜你成功得到了面试官对你手撕代码能力的基本认可🎉。但是面试官往往会不止于此，将继续考察面试者的各种能力。</p>
<h5 id="第二问：用-reduce-实现-flat-函数"><a href="#第二问：用-reduce-实现-flat-函数" class="headerlink" title="第二问：用 reduce 实现 flat 函数"></a>第二问：用 <code>reduce</code> 实现 <code>flat</code> 函数</h5><p>我见过很多的面试官都很喜欢点名道姓的要面试者直接用 <code>reduce</code> 去实现 <code>flat</code> 函数。想知道为什么？文章后半篇我们考虑数组空位的情况的时候就知道为啥了。其实思路也是一样的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;]</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 首先使用 reduce 展开一层</span><br><span class="line">arr.reduce((pre, cur) &#x3D;&gt; pre.concat(cur), []);</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 用 reduce 展开一层 + 递归</span><br><span class="line">const flat &#x3D; arr &#x3D;&gt; &#123;</span><br><span class="line">  return arr.reduce((pre, cur) &#x3D;&gt; &#123;</span><br><span class="line">    return pre.concat(Array.isArray(cur) ? flat(cur) : cur);</span><br><span class="line">  &#125;, []);</span><br><span class="line">&#125;;</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<h5 id="第三问：使用栈的思想实现-flat-函数"><a href="#第三问：使用栈的思想实现-flat-函数" class="headerlink" title="第三问：使用栈的思想实现 flat 函数"></a>第三问：使用栈的思想实现 <code>flat</code> 函数</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 栈思想</span><br><span class="line">function flat(arr) &#123;</span><br><span class="line">  const result &#x3D; []; </span><br><span class="line">  const stack &#x3D; [].concat(arr);  &#x2F;&#x2F; 将数组元素拷贝至栈，直接赋值会改变原数组</span><br><span class="line">  &#x2F;&#x2F;如果栈不为空，则循环遍历</span><br><span class="line">  while (stack.length !&#x3D;&#x3D; 0) &#123;</span><br><span class="line">    const val &#x3D; stack.pop(); </span><br><span class="line">    if (Array.isArray(val)) &#123;</span><br><span class="line">      stack.push(...val); &#x2F;&#x2F;如果是数组再次入栈，并且展开了一层</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      result.unshift(val); &#x2F;&#x2F;如果不是数组就将其取出来放入结果数组中</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  return result;</span><br><span class="line">&#125;</span><br><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;]</span><br><span class="line">flat(arr)</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<h5 id="第四问：通过传入整数参数控制“拉平”层数"><a href="#第四问：通过传入整数参数控制“拉平”层数" class="headerlink" title="第四问：通过传入整数参数控制“拉平”层数"></a>第四问：通过传入整数参数控制“拉平”层数</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; reduce + 递归</span><br><span class="line">function flat(arr, num &#x3D; 1) &#123;</span><br><span class="line">  return num &gt; 0</span><br><span class="line">    ? arr.reduce(</span><br><span class="line">        (pre, cur) &#x3D;&gt;</span><br><span class="line">          pre.concat(Array.isArray(cur) ? flat(cur, num - 1) : cur),</span><br><span class="line">        []</span><br><span class="line">      )</span><br><span class="line">    : arr.slice();</span><br><span class="line">&#125;</span><br><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;]</span><br><span class="line">flat(arr, Infinity);</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<h5 id="第五问：使用-Generator-实现-flat-函数"><a href="#第五问：使用-Generator-实现-flat-函数" class="headerlink" title="第五问：使用 Generator 实现 flat 函数"></a>第五问：使用 <code>Generator</code> 实现 <code>flat</code> 函数</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">function* flat(arr, num) &#123;</span><br><span class="line">  if (num &#x3D;&#x3D;&#x3D; undefined) num &#x3D; 1;</span><br><span class="line">  for (const item of arr) &#123;</span><br><span class="line">    if (Array.isArray(item) &amp;&amp; num &gt; 0) &#123;   &#x2F;&#x2F; num &gt; 0</span><br><span class="line">      yield* flat(item, num - 1);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      yield item;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br><span class="line">&#x2F;&#x2F; 调用 Generator 函数后，该函数并不执行，返回的也不是函数运行结果，而是一个指向内部状态的指针对象。</span><br><span class="line">&#x2F;&#x2F; 也就是遍历器对象（Iterator Object）。所以我们要用一次扩展运算符得到结果</span><br><span class="line">[...flat(arr, Infinity)];</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<h5 id="第六问：实现在原型链上重写-flat-函数"><a href="#第六问：实现在原型链上重写-flat-函数" class="headerlink" title="第六问：实现在原型链上重写 flat 函数"></a>第六问：实现在原型链上重写 <code>flat</code> 函数</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Array.prototype.fakeFlat &#x3D; function(num &#x3D; 1) &#123;</span><br><span class="line">  if (!Number(num) || Number(num) &lt; 0) &#123;</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line">  let arr &#x3D; this.concat();    &#x2F;&#x2F; 获得调用 fakeFlat 函数的数组</span><br><span class="line">  while (num &gt; 0) &#123;           </span><br><span class="line">    if (arr.some(x &#x3D;&gt; Array.isArray(x))) &#123;</span><br><span class="line">      arr &#x3D; [].concat.apply([], arr);    &#x2F;&#x2F; 数组中还有数组元素的话并且 num &gt; 0，继续展开一层数组 </span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      break; &#x2F;&#x2F; 数组中没有数组元素并且不管 num 是否依旧大于 0，停止循环。</span><br><span class="line">    &#125;</span><br><span class="line">    num--;</span><br><span class="line">  &#125;</span><br><span class="line">  return arr;</span><br><span class="line">&#125;;</span><br><span class="line">const arr &#x3D; [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;]</span><br><span class="line">arr.fakeFlat(Infinity)</span><br><span class="line">&#x2F;&#x2F; [1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3, 5, &quot;string&quot;, &#123; name: &quot;弹铁蛋同学&quot; &#125;];</span><br></pre></td></tr></table></figure>

<h5 id="第七问：考虑数组空位的情况"><a href="#第七问：考虑数组空位的情况" class="headerlink" title="第七问：考虑数组空位的情况"></a>第七问：考虑数组空位的情况</h5><p>由最开始我们总结的 <code>flat</code> 特性知道，<code>flat</code> 函数执行是会跳过空位的。ES5 大多数数组方法对空位的处理都会选择跳过空位包括：<code>forEach()</code>, <code>filter()</code>, <code>reduce()</code>, <code>every()</code> 和 <code>some()</code> 都会跳过空位。</p>
<p>所以我们可以利用上面几种方法来实现 flat 跳过空位的特性</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; reduce + 递归</span><br><span class="line">Array.prototype.fakeFlat &#x3D; function(num &#x3D; 1) &#123;</span><br><span class="line">  if (!Number(num) || Number(num) &lt; 0) &#123;</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line">  let arr &#x3D; [].concat(this);</span><br><span class="line">  return num &gt; 0</span><br><span class="line">    ? arr.reduce(</span><br><span class="line">        (pre, cur) &#x3D;&gt;</span><br><span class="line">          pre.concat(Array.isArray(cur) ? cur.fakeFlat(--num) : cur),</span><br><span class="line">        []</span><br><span class="line">      )</span><br><span class="line">    : arr.slice();</span><br><span class="line">&#125;;</span><br><span class="line">const arr &#x3D; [1, [3, 4], , ,];</span><br><span class="line">arr.fakeFlat()</span><br><span class="line">&#x2F;&#x2F; [1, 3, 4]</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; foEach + 递归</span><br><span class="line">Array.prototype.fakeFlat &#x3D; function(num &#x3D; 1) &#123;</span><br><span class="line">  if (!Number(num) || Number(num) &lt; 0) &#123;</span><br><span class="line">    return this;</span><br><span class="line">  &#125;</span><br><span class="line">  let arr &#x3D; [];</span><br><span class="line">  this.forEach(item &#x3D;&gt; &#123;</span><br><span class="line">    if (Array.isArray(item)) &#123;</span><br><span class="line">      arr &#x3D; arr.concat(item.fakeFlat(--num));</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      arr.push(item);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">  return arr;</span><br><span class="line">&#125;;</span><br><span class="line">const arr &#x3D; [1, [3, 4], , ,];</span><br><span class="line">arr.fakeFlat()</span><br><span class="line">&#x2F;&#x2F; [1, 3, 4]</span><br></pre></td></tr></table></figure>

<h5 id="扩展阅读：由于空位的处理规则非常不统一，所以建议避免出现空位。"><a href="#扩展阅读：由于空位的处理规则非常不统一，所以建议避免出现空位。" class="headerlink" title="扩展阅读：由于空位的处理规则非常不统一，所以建议避免出现空位。"></a>扩展阅读：<strong>由于空位的处理规则非常不统一，所以建议避免出现空位。</strong></h5><p><strong>ES5 对空位的处理，就非常不一致，大多数情况下会忽略空位。</strong></p>
<ul>
<li><code>forEach()</code>, <code>filter()</code>, <code>reduce()</code>, <code>every()</code> 和<code>some()</code> 都会跳过空位。</li>
<li><code>map()</code> 会跳过空位，但会保留这个值。</li>
<li><code>join()</code> 和 <code>toString()</code> 会将空位视为 <code>undefined</code>，而<code>undefined</code> 和 <code>null</code> 会被处理成空字符串。</li>
</ul>
<p><strong>ES6 明确将空位转为 <code>undefined</code>。</strong></p>
<ul>
<li><code>entries()</code>、<code>keys()</code>、<code>values()</code>、<code>find()</code> 和 <code>findIndex()</code> 会将空位处理成 <code>undefined</code>。</li>
<li><code>for...of</code> 循环会遍历空位。</li>
<li><code>fill()</code> 会将空位视为正常的数组位置。</li>
<li><code>copyWithin()</code> 会连空位一起拷贝。</li>
<li>扩展运算符（<code>...</code>）也会将空位转为 <code>undefined</code>。</li>
<li><code>Array.from</code> 方法会将数组的空位，转为 <code>undefined</code>。</li>
</ul>
<h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>面试官现场考察一道写代码的题目，其实不仅仅是写代码，在写代码的过程中会遇到各种各样的知识点和代码的边界情况。虽然大多数情况下，面试官不会那么变态，就 <code>flat</code> 实现去连续追问面试者，并且手撕好几个版本，但面试官会要求在你写的那版代码的基础上再写出一个更完美的版本是常有的事情。只有我们沉下心来把基础打扎实，不管面试官如何追问，我们都能自如的应对。<code>flat</code> 的实现绝对不会只有文中列出的这几个版本，敲出自己的代码是最好的进步，在评论区或者在 <a target="_blank" rel="noopener" href="https://github.com/NieZhuZhu/Blog/issues/2">issue</a> 中写出你自己的版本吧！</p>
<h4 id="基本实现"><a href="#基本实现" class="headerlink" title="基本实现"></a>基本实现</h4><p>递归调用</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">function flat (array) &#123;</span><br><span class="line">    let result &#x3D; [];</span><br><span class="line">    for (let i &#x3D; 0; i &lt; array.length; i++) &#123;</span><br><span class="line">        if (Array.isArray(array[i])) &#123;</span><br><span class="line">        	result &#x3D; result.concat(flat(array[i]));</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">        	result.push(array[i]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="使用reduce简化"><a href="#使用reduce简化" class="headerlink" title="使用reduce简化"></a>使用reduce简化</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">flatten</span>(<span class="params">array</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> array.reduce(</span><br><span class="line">        (target, current) =&gt; <span class="built_in">Array</span>.isArray(current) ? target.concat(flatten(current)) : target.concat(current)</span><br><span class="line">    , [])</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="直接调用"><a href="#直接调用" class="headerlink" title="直接调用"></a>直接调用</h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一种处理</span></span><br><span class="line">arr_flat = arr.flat(<span class="literal">Infinity</span>);</span><br></pre></td></tr></table></figure>

<h4 id="正则表达式"><a href="#正则表达式" class="headerlink" title="正则表达式"></a>正则表达式</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第二种处理</span><br><span class="line">ary &#x3D; arr.toSting()).replace(&#x2F;(\[|\])&#x2F;g, &#39;&#39;).split(&#39;,&#39;);</span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第三种处理：递归处理</span></span><br><span class="line"><span class="keyword">let</span> result = [];</span><br><span class="line"><span class="keyword">let</span> fn = <span class="function"><span class="keyword">function</span>(<span class="params">ary</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; ary.length; i++) &#125;&#123;</span><br><span class="line">    <span class="keyword">let</span> item = ary[i];</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(ary[i]))&#123;</span><br><span class="line">      fn(item);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      result.push(item);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="最值"><a href="#最值" class="headerlink" title="最值"></a>最值</h3><h4 id="reduce"><a href="#reduce" class="headerlink" title="reduce"></a>reduce</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">array.reduce((c,n) &#x3D;&gt; Math.max(c,n));</span><br></pre></td></tr></table></figure>

<h4 id="Math-max"><a href="#Math-max" class="headerlink" title="Math.max"></a>Math.max</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">const array &#x3D; [3,2,1,4,5];</span><br><span class="line">Math.max.apply(null, array);</span><br><span class="line">Math.max(...array);</span><br></pre></td></tr></table></figure>

<h3 id="使用reduce实现map"><a href="#使用reduce实现map" class="headerlink" title="使用reduce实现map"></a>使用reduce实现map</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.reduceToMap = <span class="function"><span class="keyword">function</span> (<span class="params">handler</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.reduce(<span class="function">(<span class="params">target, current, index</span>) =&gt;</span> &#123;</span><br><span class="line">        target.push(handler.call(<span class="built_in">this</span>, current, index))</span><br><span class="line">        <span class="keyword">return</span> target;</span><br><span class="line">    &#125;, [])</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="使用reduce实现filter"><a href="#使用reduce实现filter" class="headerlink" title="使用reduce实现filter"></a>使用reduce实现filter</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.reduceToFilter = <span class="function"><span class="keyword">function</span> (<span class="params">handler</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.reduce(<span class="function">(<span class="params">target, current, index</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (handler.call(<span class="built_in">this</span>, current, index)) &#123;</span><br><span class="line">        	target.push(current);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> target;</span><br><span class="line">    &#125;, [])</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="7-数组乱序-洗牌算法"><a href="#7-数组乱序-洗牌算法" class="headerlink" title="7.数组乱序-洗牌算法"></a>7.数组乱序-洗牌算法</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">disorder</span>(<span class="params">array</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> length = array.length;</span><br><span class="line">    <span class="keyword">let</span> current = length - <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">let</span> random;</span><br><span class="line">    <span class="keyword">while</span> (current &gt; -<span class="number">1</span>) &#123;</span><br><span class="line">        random = <span class="built_in">Math</span>.floor(length * <span class="built_in">Math</span>.random());</span><br><span class="line">        [array[current], array[random]] = [array[random], array[current]];</span><br><span class="line">        current--;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> array;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="8-函数柯里化"><a href="#8-函数柯里化" class="headerlink" title="8.函数柯里化"></a>8.函数柯里化</h2><h3 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h3><p>把接受多个参数的函数变换成接受一个单一参数（最初函数的第一个参数）的函数，并且返回接受余下的参数且返回结果的新函数的技术</p>
<p>通俗易懂的解释：用闭包把参数保存起来，当参数的数量足够执行函数了，就开始执行函数。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">function curry(fn, args &#x3D; []) &#123;</span><br><span class="line">    let length &#x3D; fn.length;</span><br><span class="line">    return function() &#123;</span><br><span class="line">        newArgs &#x3D; args.concat(Array.prototype.slice.call(arguments));</span><br><span class="line">        if (newArgs.length &lt; length) &#123;</span><br><span class="line">            return curry.call(this, fn, newArgs);</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">            return fn.apply(this, newArgs);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function multiFn(a, b, c) &#123;</span><br><span class="line">    return a * b * c;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var multi &#x3D; curry(multiFn);</span><br><span class="line"></span><br><span class="line">console.log(multi(2)(3)(4)); &#x2F;&#x2F; 24</span><br><span class="line">console.log(multi(2,3,4)); &#x2F;&#x2F; 24</span><br><span class="line">console.log(multi(2)(3,4)); &#x2F;&#x2F; 24</span><br><span class="line">console.log(multi(2,3)(4)); &#x2F;&#x2F; 24</span><br></pre></td></tr></table></figure>

<h3 id="ES6写法"><a href="#ES6写法" class="headerlink" title="ES6写法"></a>ES6写法</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">const curry &#x3D; (fn, arr &#x3D; []) &#x3D;&gt; (...args) &#x3D;&gt; (</span><br><span class="line">  arg &#x3D;&gt; arg.length &#x3D;&#x3D;&#x3D; fn.length ? fn(...arg) : curry(fn, arg)</span><br><span class="line">)([...arr, ...args])</span><br><span class="line"></span><br><span class="line">function multiFn(a, b, c) &#123;</span><br><span class="line">    return a * b * c;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var multi &#x3D; curry(multiFn);</span><br><span class="line"></span><br><span class="line">console.log(multi(2)(3)(4));</span><br><span class="line">console.log(multi(2,3,4));</span><br><span class="line">console.log(multi(2)(3,4));</span><br><span class="line">console.log(multi(2,3)(4));</span><br></pre></td></tr></table></figure>

<h3 id="简单写法版"><a href="#简单写法版" class="headerlink" title="简单写法版"></a>简单写法版</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">function currying(fn, ...args) &#123;</span><br><span class="line">    if (args.length &gt;&#x3D; fn.length) &#123;</span><br><span class="line">    	&#x2F;&#x2F; 判断当前函数传入的参数是否大于或等于fn需要参数的数量，如果是，直接执行fn</span><br><span class="line">    	return fn(...args);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">    	&#x2F;&#x2F; 如果传入参数数量不够，返回一个闭包，暂存传入的参数，并重新返回currying函数</span><br><span class="line">    	return (...args2) &#x3D;&gt; curry(fn, ...args, ...args2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function multiFn(a, b, c) &#123;</span><br><span class="line">    return a * b * c;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var multi &#x3D; curry(multiFn);</span><br><span class="line"></span><br><span class="line">console.log(multi(2)(3)(4)); &#x2F;&#x2F; 24</span><br><span class="line">console.log(multi(3, 4, 5)); &#x2F;&#x2F; 60</span><br><span class="line">console.log(multi(4)(5, 6)); &#x2F;&#x2F; 120</span><br><span class="line">console.log(multi(5, 6)(7)); &#x2F;&#x2F; 210</span><br></pre></td></tr></table></figure>

<h2 id="9-手动实现JSONP"><a href="#9-手动实现JSONP" class="headerlink" title="9.手动实现JSONP"></a>9.手动实现JSONP</h2><h3 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h3><p><code>JSONP</code> 的原理很简单，就是利用 <code>&lt;script&gt;</code> 标签没有跨域限制的漏洞。通过 <code>&lt;script&gt;</code>标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时</p>
<ul>
<li>1.将传入的data数据转化为url字符串形式</li>
<li>2.处理url中的回调函数</li>
<li>3.创建一个script标签并插入到页面中</li>
<li>4.挂载回调函数</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">(function (window,document) &#123;</span><br><span class="line">    &quot;use strict&quot;;</span><br><span class="line">    var jsonp &#x3D; function (url, data, callback) &#123;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 1.将传入的data数据转化为url字符串形式</span><br><span class="line">        &#x2F;&#x2F; 例子&#123;id:1,name:&#39;jack&#39;&#125; &#x3D;&gt; id&#x3D;1&amp;name&#x3D;jack</span><br><span class="line">        var dataString &#x3D; url.indexof(&#39;?&#39;) &#x3D;&#x3D; -1? &#39;?&#39;: &#39;&amp;&#39;;</span><br><span class="line">        for(var key in data)&#123;</span><br><span class="line">            dataString +&#x3D; key + &#39;&#x3D;&#39; + data[key] + &#39;&amp;&#39;;</span><br><span class="line">        &#125;;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 2 处理url中的回调函数</span><br><span class="line">        &#x2F;&#x2F; cbFuncName回调函数的名字 ：my_json_cb_名字的前缀 + 随机数（把小数点去掉）</span><br><span class="line">        var cbFuncName &#x3D; &#39;my_json_cb_&#39; + Math.random().toString().replace(&#39;.&#39;,&#39;&#39;);</span><br><span class="line">        dataString +&#x3D; &#39;callback&#x3D;&#39; + cbFuncName;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 3.创建一个script标签并插入到页面中</span><br><span class="line">        var scriptEle &#x3D; document.createElement(&#39;script&#39;);</span><br><span class="line">        scriptEle.src &#x3D; url + dataString;</span><br><span class="line"></span><br><span class="line">        &#x2F;&#x2F; 4.挂载回调函数</span><br><span class="line">        window[cbFuncName] &#x3D; function (data) &#123;</span><br><span class="line">            callback(data);</span><br><span class="line">            &#x2F;&#x2F; 处理完回调函数的数据之后，删除jsonp的script标签</span><br><span class="line">            document.body.removeChild(scriptEle);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        document.body.appendChild(scriptEle);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    window.$jsonp &#x3D; jsonp;</span><br><span class="line"></span><br><span class="line">&#125;)(window,document)</span><br></pre></td></tr></table></figure>

<h3 id="简单实现"><a href="#简单实现" class="headerlink" title="简单实现"></a>简单实现</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">jsonp</span>(<span class="params">url, jsonpCallback, success</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 利用script属性</span></span><br><span class="line">    <span class="keyword">let</span> script = <span class="built_in">document</span>.createElement(<span class="string">&#x27;script&#x27;</span>);</span><br><span class="line">    script.src = url;</span><br><span class="line">    script.async = <span class="literal">true</span>;</span><br><span class="line">    script.type = <span class="string">&#x27;text/javascript&#x27;</span>;</span><br><span class="line">    <span class="built_in">window</span>[jsonpCallback] = <span class="function"><span class="keyword">function</span>(<span class="params">data</span>) </span>&#123;</span><br><span class="line">    	success &amp;&amp; success(data);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">document</span>.body.appendChild(script);</span><br><span class="line">&#125;</span><br><span class="line">jsonp(<span class="string">&#x27;http://xxx&#x27;</span>, <span class="string">&#x27;callback&#x27;</span>, <span class="function"><span class="keyword">function</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(value);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="10-模拟实现promise"><a href="#10-模拟实现promise" class="headerlink" title="10.模拟实现promise"></a>10.模拟实现promise</h2><p>为什么用Promise？在传统的异步编程中，如果异步之间存在依赖关系，我们就需要通过层层嵌套回调来满足这种依赖，如果嵌套层数过多，可读性和可维护性都变得很差，产生所谓“回调地狱”，而Promise将回调嵌套改为链式调用，增加可读性和可维护性。</p>
<p>Promise有三种状态.，Promise一旦新建就立刻执行, 此时的状态是Pending(进行中),它接受两个参数分别是resolve和reject。它们是两个函数.<br> resolve函数的作用是将Promise对象的状态从’未完成’变为’成功’(由Pending变为Resolved), 在异步操作成功时,将操作结果作为参数传递出去;<br> reject函数的作用是将Promise对象的状态从’未完成’变为失败(由Pending变为Rejected),在异步操作失败时调用,并将异步操作的错误作为参数传递出去.</p>
<h3 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 使用</span><br><span class="line">var promise &#x3D; new Promise((resolve,reject) &#x3D;&gt; &#123;</span><br><span class="line">    if (操作成功) &#123;</span><br><span class="line">        resolve(value)</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        reject(error)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line">promise.then(function (value) &#123;</span><br><span class="line">    &#x2F;&#x2F; success</span><br><span class="line">&#125;,function (value) &#123;</span><br><span class="line">    &#x2F;&#x2F; failure</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="基础版本"><a href="#基础版本" class="headerlink" title="基础版本"></a>基础版本</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">myPromise</span>(<span class="params">constructor</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> self = <span class="built_in">this</span>;</span><br><span class="line">    self.status = <span class="string">&quot;pending&quot;</span>   <span class="comment">// 定义状态改变前的初始状态</span></span><br><span class="line">    self.value = <span class="literal">undefined</span>;   <span class="comment">// 定义状态为resolved的时候的状态</span></span><br><span class="line">    self.reason = <span class="literal">undefined</span>;  <span class="comment">// 定义状态为rejected的时候的状态</span></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">resolve</span>(<span class="params">value</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(self.status === <span class="string">&quot;pending&quot;</span>) &#123;</span><br><span class="line">            self.value = value;</span><br><span class="line">            self.status = <span class="string">&quot;resolved&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">reject</span>(<span class="params">reason</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(self.status === <span class="string">&quot;pending&quot;</span>) &#123;</span><br><span class="line">            self.reason = reason;</span><br><span class="line">            self.status = <span class="string">&quot;rejected&quot;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 捕获构造异常</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">    	<span class="title">constructor</span>(<span class="params">resolve, reject</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">    	reject(e);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="then方法"><a href="#then方法" class="headerlink" title="then方法"></a>then方法</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 添加 then 方法</span><br><span class="line">myPromise.prototype.then &#x3D; function(onFullfilled, onRejected) &#123;</span><br><span class="line">   let self &#x3D; this;</span><br><span class="line">   switch(self.status) &#123;</span><br><span class="line">      case &quot;resolved&quot;:</span><br><span class="line">        onFullfilled(self.value);</span><br><span class="line">        break;</span><br><span class="line">      case &quot;rejected&quot;:</span><br><span class="line">        onRejected(self.reason);</span><br><span class="line">        break;</span><br><span class="line">      default:       </span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var p &#x3D; new myPromise(function(resolve,reject) &#123;</span><br><span class="line">    resolve(1)</span><br><span class="line">&#125;);</span><br><span class="line">p.then(function(x) &#123;</span><br><span class="line">    console.log(x) &#x2F;&#x2F; 1</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="catch方法"><a href="#catch方法" class="headerlink" title="catch方法"></a>catch方法</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">MyPromise.prototype.catch &#x3D; function(onRejected) &#123;</span><br><span class="line">	return this.then(null, onRejected);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="finally方法"><a href="#finally方法" class="headerlink" title="finally方法"></a>finally方法</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">MyPromise.prototype.finally &#x3D; function(fn) &#123;</span><br><span class="line">    return this.then(value &#x3D;&gt; &#123;</span><br><span class="line">       fn();</span><br><span class="line">       return value;</span><br><span class="line">    &#125;, reason &#x3D;&gt; &#123;</span><br><span class="line">        fn();</span><br><span class="line">        throw reason;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="面试够用版-1"><a href="#面试够用版-1" class="headerlink" title="面试够用版"></a>面试够用版</h3><h4 id="promise"><a href="#promise" class="headerlink" title="promise"></a>promise</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">class Promise &#123;</span><br><span class="line">    constructor(fn) &#123;</span><br><span class="line">        &#x2F;&#x2F;三个状态</span><br><span class="line">        this.status &#x3D; &#39;pending&#39;,</span><br><span class="line">        this.resolve &#x3D; undefined;</span><br><span class="line">        this.reject &#x3D; undefined;</span><br><span class="line">        let resolve &#x3D; value &#x3D;&gt; &#123;</span><br><span class="line">            if (this.status &#x3D;&#x3D;&#x3D; &#39;pending&#39;) &#123;</span><br><span class="line">                this.status &#x3D; &#39;resolved&#39;;</span><br><span class="line">                this.resolve &#x3D; value;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;;</span><br><span class="line">        let reject &#x3D; value &#x3D;&gt; &#123;</span><br><span class="line">            if (this.status &#x3D;&#x3D;&#x3D; &#39;pending&#39;) &#123;</span><br><span class="line">                this.status &#x3D; &#39;rejected&#39;;</span><br><span class="line">                this.reject &#x3D; value;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        try &#123;</span><br><span class="line">            fn(resolve, reject)</span><br><span class="line">        &#125; catch (e) &#123;</span><br><span class="line">            reject(e)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    then(onResolved, onRejected) &#123;</span><br><span class="line">        switch (this.status) &#123;</span><br><span class="line">            case &#39;resolved&#39;: onResolved(this.resolve); break;</span><br><span class="line">            case &#39;rejected&#39;: onRejected(this.resolve); break;</span><br><span class="line">            default:</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    catch(onRejected) &#123;</span><br><span class="line">        return this.then(null, onRejected);</span><br><span class="line">    &#125;;</span><br><span class="line">    finally (fn) &#123;</span><br><span class="line">        return this.then(value &#x3D;&gt; &#123;</span><br><span class="line">           fn();</span><br><span class="line">           return value;</span><br><span class="line">        &#125;, reason &#x3D;&gt; &#123;</span><br><span class="line">            fn();</span><br><span class="line">            throw reason;</span><br><span class="line">        &#125;);</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="Promise-all"><a href="#Promise-all" class="headerlink" title="Promise.all"></a>Promise.all</h4><p>Promise.all() 它接收一个promise对象组成的数组作为参数，并返回一个新的promise对象。</p>
<p>当数组中所有的对象都resolve时，新对象状态变为fulfilled，所有对象的resolve的value依次添加组成一个新的数组，并以新的数组作为新对象resolve的value。<br>当数组中有一个对象reject时，新对象状态变为rejected，并以当前对象reject的reason作为新对象reject的reason。</p>
<pre><code>Promise.prototype.all(promises) &#123;
    if (!Array.isArray(promises)) &#123;
        throw new Error(&quot;promises must be an array&quot;)
    &#125;
    return new Promise(function (resolve, reject) &#123;
        let promsieNum = promises.length;
        let resolvedCount = 0;
        let resolveValues = new Array(promsieNum);
        for (let i = 0; i &lt; promsieNum; i++) &#123;
            Promise.resolve(promises[i].then(function (value) &#123;
                    resolveValues[i] = value;
                    resolvedCount++;
                    if (resolvedCount === promsieNum) &#123;
                        return resolve(resolveValues)
                    &#125;
                &#125;, function (reason) &#123;
                    return reject(reason);
                &#125;
           ))
        &#125;
    &#125;)
&#125;
</code></pre>
<h4 id="Promise-race"><a href="#Promise-race" class="headerlink" title="Promise.race"></a>Promise.race</h4><p>Promise.race() 它同样接收一个promise对象组成的数组作为参数，并返回一个新的promise对象。</p>
<p>与Promise.all()不同，它是在数组中有一个对象（最早改变状态）resolve或reject时，就改变自身的状态，并执行响应的回调。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Promise.prototype.race(promises) &#123;</span><br><span class="line">    if (!Array.isArray(promises)) &#123;</span><br><span class="line">    	throw new Error(&quot;promises must be an array&quot;)</span><br><span class="line">    &#125;</span><br><span class="line">    return new Promise(function (resolve, reject) &#123;</span><br><span class="line">        promises.forEach(p &#x3D;&gt;</span><br><span class="line">            Promise.resolve(p).then(data &#x3D;&gt; &#123;</span><br><span class="line">                resolve(data)</span><br><span class="line">            &#125;, err &#x3D;&gt; &#123;</span><br><span class="line">                reject(err)</span><br><span class="line">            &#125;)</span><br><span class="line">        )</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="11-手动实现ES5继承"><a href="#11-手动实现ES5继承" class="headerlink" title="11.手动实现ES5继承"></a>11.手动实现ES5继承</h2><p>Child继承Parent</p>
<h3 id="原型继承"><a href="#原型继承" class="headerlink" title="原型继承"></a>原型继承</h3><blockquote>
<p>子类的原型指向父类。</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Child.prototype = <span class="keyword">new</span> Parent();</span><br></pre></td></tr></table></figure>

<p>缺点：原型是所有子类实例共享的，改变一个其他也会改变。</p>
<h3 id="构造继承"><a href="#构造继承" class="headerlink" title="构造继承"></a>构造继承</h3><blockquote>
<p>在子类构造函数中调用父类构造函数</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">    Parent.call(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>缺点：不能继承父类原型，函数在构造函数中，每个子类实例不能共享函数，浪费内存。</p>
<h3 id="组合继承"><a href="#组合继承" class="headerlink" title="组合继承"></a>组合继承</h3><blockquote>
<p>使用构造继承继承父类参数，使用原型继承继承父类函数</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">    Parent.call(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Child.prototype = <span class="keyword">new</span> Parent();</span><br></pre></td></tr></table></figure>

<p>缺点：Parent的构造函数会多执行了一次（<code>Child.prototype = new Parent()</code>;）</p>
<h3 id="寄生组合继承"><a href="#寄生组合继承" class="headerlink" title="寄生组合继承"></a>寄生组合继承</h3><blockquote>
<p>将父类原型对象直接给到子类，父类构造函数只执行一次，而且父类属性和方法均能访问</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">    Parent.call(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Child.prototype = Parent.prototype;</span><br></pre></td></tr></table></figure>

<p>父类原型和子类原型是同一个对象，无法区分子类真正是由谁构造。</p>
<h3 id="寄生组合继承优化"><a href="#寄生组合继承优化" class="headerlink" title="寄生组合继承优化"></a>寄生组合继承优化</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Child</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    Parent.call(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line">Child.prototype = <span class="built_in">Object</span>.create(Parent.prototype);</span><br><span class="line">Child.prototype.constructor = Child;</span><br></pre></td></tr></table></figure>

<p>最推荐的一种方式，接近完美的继承。</p>
<h2 id="12-手动实现instanceof"><a href="#12-手动实现instanceof" class="headerlink" title="12.手动实现instanceof"></a>12.手动实现instanceof</h2><h3 id="原理-3"><a href="#原理-3" class="headerlink" title="原理"></a>原理</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a <span class="keyword">instanceof</span> <span class="built_in">Object</span></span><br></pre></td></tr></table></figure>

<p>判断<code>Object</code>的prototype是否在<code>a</code>的原型链上。</p>
<h3 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h3><p>按照target原型链的向上查找，直到找到 origin 或 null</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">myInstanceof</span>(<span class="params">target, origin</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> proto = target.__proto__;</span><br><span class="line">    <span class="keyword">if</span> (proto) &#123;</span><br><span class="line">        <span class="keyword">if</span> (origin.prototype == proto) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> myInstanceof(proto, origin)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>改用循环而不是递归  </p>
<pre><code>// target instanceof origin
// 变量origin的原型 存在于变量target的原型链上
function myInstanceof(target, origin)&#123;    
    // 验证如果为基本数据类型，就直接返回false
    const baseType = [&#39;string&#39;, &#39;number&#39;,&#39;boolean&#39;,&#39;undefined&#39;,&#39;symbol&#39;]
    if(baseType.includes(typeof(target))) return false;
    let oP  = origin.prototype;  // 取 origin 的显示原型
    proto = target.__proto__;       // 取 target 的隐式原型
    while(true)&#123;           // 无线循环的写法（也可以使 for(;;) ）
        if(proto === null)&#123;    // 找到最顶层
            return false;
        &#125;
        if(proto === oP)&#123;       // 严格相等
            return true;
        &#125;
        proto = proto.__proto__;  //没找到继续向上一层原型链查找
    &#125;
&#125;
</code></pre>
<h2 id="13-基于Promise的ajax封装"><a href="#13-基于Promise的ajax封装" class="headerlink" title="13.基于Promise的ajax封装"></a>13.基于Promise的ajax封装</h2><p>基于把原生<code>ajax</code>封装为<code>Promise</code>形式</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">ajax</span>(<span class="params">url, method = <span class="string">&#x27;get&#x27;</span>, param = &#123;&#125;</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> xhr = <span class="keyword">new</span> XMLHttpRequest();</span><br><span class="line">        <span class="keyword">const</span> paramString = getStringParam(param);</span><br><span class="line">        <span class="keyword">if</span> (method === <span class="string">&#x27;get&#x27;</span> &amp;&amp; paramString) &#123;</span><br><span class="line">        	url.indexOf(<span class="string">&#x27;?&#x27;</span>) &gt; -<span class="number">1</span> ? url += paramString : url += <span class="string">`?<span class="subst">$&#123;paramString&#125;</span>`</span></span><br><span class="line">        &#125;</span><br><span class="line">        xhr.open(method, url);</span><br><span class="line">        xhr.onload = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">            <span class="keyword">const</span> result = &#123;</span><br><span class="line">                status: xhr.status,</span><br><span class="line">                statusText: xhr.statusText,</span><br><span class="line">                headers: xhr.getAllResponseHeaders(),</span><br><span class="line">                data: xhr.response || xhr.responseText</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> ((xhr.status &gt;= <span class="number">200</span> &amp;&amp; xhr.status &lt; <span class="number">300</span>) || xhr.status == <span class="number">304</span>) &#123;</span><br><span class="line">            	resolve(result);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            	reject(result);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 设置请求头</span></span><br><span class="line">        xhr.setRequestHeader(<span class="string">&quot;Content-type&quot;</span>, <span class="string">&quot;application/x-www-form-urlencoded&quot;</span>);</span><br><span class="line">        <span class="comment">// 跨域携带cookie</span></span><br><span class="line">        xhr.withCredentials = <span class="literal">true</span>;</span><br><span class="line">        <span class="comment">// 错误处理</span></span><br><span class="line">        xhr.onerror = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        	reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">&#x27;请求出错&#x27;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        xhr.timeout = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        	reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">&#x27;请求超时&#x27;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        xhr.onabort = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">        	reject(<span class="keyword">new</span> <span class="built_in">TypeError</span>(<span class="string">&#x27;请求被终止&#x27;</span>));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (method === <span class="string">&#x27;post&#x27;</span>) &#123;</span><br><span class="line">        	xhr.send(paramString);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        	xhr.send();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getStringParam</span>(<span class="params">param</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> dataString = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> key <span class="keyword">in</span> param) &#123;</span><br><span class="line">    	dataString += <span class="string">`<span class="subst">$&#123;key&#125;</span>=<span class="subst">$&#123;param[key]&#125;</span>&amp;`</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> dataString;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="14-单例模式"><a href="#14-单例模式" class="headerlink" title="14.单例模式"></a>14.单例模式</h2><p>在合适的时候才创建对像，并且只创建唯一的一个。创建对象和管理单例的职责被分布在两个不同的方法中，这两个方法组合起来才具有单例模式的威力。使用闭包实现：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">function Singleton (name) &#123;</span><br><span class="line">	this.name &#x3D; name;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Single.getInstance &#x3D; (function(name) &#123;</span><br><span class="line">	let instance;</span><br><span class="line">	return function(name) &#123;</span><br><span class="line">		if (!instance) &#123;</span><br><span class="line">			instance &#x3D; new Singleton(name);</span><br><span class="line">		&#125;</span><br><span class="line">		return instance;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;)();</span><br><span class="line"></span><br><span class="line">var a &#x3D; Singleton.getInstance(&#39;ConardLi&#39;);</span><br><span class="line">var b &#x3D; Singleton.getInstance(&#39;ConardLi2&#39;);</span><br><span class="line"></span><br><span class="line">console.log(a &#x3D;&#x3D;&#x3D; b);   &#x2F;&#x2F;true</span><br></pre></td></tr></table></figure>

<blockquote>
<p>另一种实现方式，核心要点: 用闭包和Proxy属性拦截</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">proxy</span>(<span class="params">func</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> instance;</span><br><span class="line">    <span class="keyword">let</span> handler = &#123;</span><br><span class="line">      	<span class="title">constructor</span> (<span class="params">target, args</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!instance) &#123;</span><br><span class="line">                instance = <span class="built_in">Reflect</span>.constructor(fun, args);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> instance;</span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Proxy</span>(func, handler);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="15-异步循环打印"><a href="#15-异步循环打印" class="headerlink" title="15.异步循环打印"></a>15.异步循环打印</h2><p>使用<code>promise + async await</code>实现异步循环打印</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sleep = <span class="function"><span class="keyword">function</span> (<span class="params">time, i</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span> (<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">        <span class="built_in">setTimeout</span>(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">            resolve(i);</span><br><span class="line">        &#125;, time);</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> start = <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">6</span>; i++) &#123;</span><br><span class="line">        <span class="keyword">let</span> result = <span class="keyword">await</span> sleep(<span class="number">1000</span>, i);</span><br><span class="line">        <span class="built_in">console</span>.log(result);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">start();</span><br></pre></td></tr></table></figure>

<h2 id="16-图片懒加载"><a href="#16-图片懒加载" class="headerlink" title="16.图片懒加载"></a>16.图片懒加载</h2><h3 id="监听图片高度"><a href="#监听图片高度" class="headerlink" title="监听图片高度"></a>监听图片高度</h3><p>图片，用一个其他属性存储真正的图片地址：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;loading.gif&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;https://cdn.pixabay.com/photo/2015/09/09/16/05/forest-931706_1280.jpg&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;loading.gif&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;https://cdn.pixabay.com/photo/2014/08/01/00/08/pier-407252_1280.jpg&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;loading.gif&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;https://cdn.pixabay.com/photo/2014/12/15/17/16/pier-569314_1280.jpg&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;loading.gif&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;https://cdn.pixabay.com/photo/2010/12/13/10/09/abstract-2384_1280.jpg&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&quot;loading.gif&quot;</span> <span class="attr">data-src</span>=<span class="string">&quot;https://cdn.pixabay.com/photo/2015/10/24/11/09/drop-of-water-1004250_1280.jpg&quot;</span></span></span><br></pre></td></tr></table></figure>

<p>通过图片<code>offsetTop</code>和<code>window</code>的<code>innerHeight</code>，<code>scrollTop</code>判断图片是否位于可视区域。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> img = <span class="built_in">document</span>.getElementsByTagName(<span class="string">&quot;img&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> n = <span class="number">0</span>; <span class="comment">//存储图片加载到的位置，避免每次都从第一张图片开始遍历</span></span><br><span class="line">lazyload(); <span class="comment">//页面载入完毕加载可是区域内的图片</span></span><br><span class="line"><span class="comment">// 节流函数，保证每200ms触发一次</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">throttle</span>(<span class="params">event, time</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> timer = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span> (<span class="params">...args</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!timer) &#123;</span><br><span class="line">            timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">                timer = <span class="literal">null</span>;</span><br><span class="line">                event.apply(<span class="built_in">this</span>, args);</span><br><span class="line">            &#125;, time);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">&#x27;scroll&#x27;</span>, throttle(lazyload, <span class="number">200</span>))</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">lazyload</span>(<span class="params"></span>) </span>&#123; <span class="comment">//监听页面滚动事件</span></span><br><span class="line">    <span class="keyword">var</span> seeHeight = <span class="built_in">window</span>.innerHeight; <span class="comment">//可见区域高度</span></span><br><span class="line">    <span class="keyword">var</span> scrollTop = <span class="built_in">document</span>.documentElement.scrollTop || <span class="built_in">document</span>.body.scrollTop; <span class="comment">//滚动条距离顶部高度</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> i = n; i &lt; img.length; i++) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(img[i].offsetTop, seeHeight, scrollTop);</span><br><span class="line">        <span class="keyword">if</span> (img[i].offsetTop &lt; seeHeight + scrollTop) &#123;</span><br><span class="line">            <span class="keyword">if</span> (img[i].getAttribute(<span class="string">&quot;src&quot;</span>) == <span class="string">&quot;loading.gif&quot;</span>) &#123;</span><br><span class="line">                img[i].src = img[i].getAttribute(<span class="string">&quot;data-src&quot;</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            n = i + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="IntersectionObserver"><a href="#IntersectionObserver" class="headerlink" title="IntersectionObserver"></a>IntersectionObserver</h3><blockquote>
<p>IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。</p>
</blockquote>
<p><code>Intersection Observer</code>可以不用监听<code>scroll</code>事件，做到元素一可见便调用回调，在回调里面我们来判断元素是否可见。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (IntersectionObserver) &#123;</span><br><span class="line">    <span class="keyword">let</span> lazyImageObserver = <span class="keyword">new</span> IntersectionObserver(<span class="function">(<span class="params">entries, observer</span>) =&gt;</span> &#123;</span><br><span class="line">        entries.forEach(<span class="function">(<span class="params">entry, index</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> lazyImage = entry.target;</span><br><span class="line">            <span class="comment">// 如果元素可见            </span></span><br><span class="line">            <span class="keyword">if</span> (entry.intersectionRatio &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (lazyImage.getAttribute(<span class="string">&quot;src&quot;</span>) == <span class="string">&quot;loading.gif&quot;</span>) &#123;</span><br><span class="line">                    lazyImage.src = lazyImage.getAttribute(<span class="string">&quot;data-src&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                lazyImageObserver.unobserve(lazyImage)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; img.length; i++) &#123;</span><br><span class="line">        lazyImageObserver.observe(img[i]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="17-模拟Object-create"><a href="#17-模拟Object-create" class="headerlink" title="17.模拟Object.create"></a>17.模拟Object.create</h2><blockquote>
<p>Object.create()方法创建一个新对象，使用现有的对象来提供新创建的对象的<strong>proto</strong></p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 模拟 Object.create</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">create</span> (<span class="params">proto</span>) </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">F</span>(<span class="params"></span>) </span>&#123;&#125;</span><br><span class="line">    F.prototype = proto;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> F();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="18-实现一个JSON-stringify"><a href="#18-实现一个JSON-stringify" class="headerlink" title="18.实现一个JSON.stringify"></a>18.实现一个JSON.stringify</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">JSON</span>.stringify(value[, replacer [, space]])：</span><br></pre></td></tr></table></figure>

<ul>
<li><code>Boolean | Number| String</code>类型会自动转换成对应的原始值。</li>
<li><code>undefined</code>、任意函数以及<code>symbol</code>，会被忽略（出现在非数组对象的属性值中时），或者被转换成 <code>null</code>（出现在数组中时）。</li>
<li>不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身，即循环引用，属性也会被忽略</li>
<li>如果一个对象的属性值通过某种间接的方式指回该对象本身，即循环引用，属性也会被忽略</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">jsonStringify</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> type = <span class="keyword">typeof</span> obj;</span><br><span class="line">    <span class="keyword">if</span> (type !== <span class="string">&quot;object&quot;</span>) &#123;</span><br><span class="line">    	<span class="comment">// 不是字符串 undefined 和 function 类型</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="regexp">/string|undefined|function/</span>.test(type)) &#123;</span><br><span class="line">            obj = <span class="string">&#x27;&quot;&#x27;</span> + obj + <span class="string">&#x27;&quot;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">String</span>(obj);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    	<span class="comment">// JSON为空数组</span></span><br><span class="line">        <span class="keyword">let</span> json = []</span><br><span class="line">        <span class="comment">// 是否为数组</span></span><br><span class="line">        <span class="keyword">let</span> arr = <span class="built_in">Array</span>.isArray(obj)</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">let</span> k <span class="keyword">in</span> obj) &#123;</span><br><span class="line">            <span class="keyword">let</span> v = obj[k];</span><br><span class="line">            <span class="keyword">let</span> type = <span class="keyword">typeof</span> v;</span><br><span class="line">            <span class="keyword">if</span> (<span class="regexp">/string|undefined|function/</span>.test(type)) &#123;</span><br><span class="line">                v = <span class="string">&#x27;&quot;&#x27;</span> + v + <span class="string">&#x27;&quot;&#x27;</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (type === <span class="string">&quot;object&quot;</span>) &#123;</span><br><span class="line">                v = jsonStringify(v);</span><br><span class="line">            &#125;</span><br><span class="line">            json.push((arr ? <span class="string">&quot;&quot;</span> : <span class="string">&#x27;&quot;&#x27;</span> + k + <span class="string">&#x27;&quot;:&#x27;</span>) + <span class="built_in">String</span>(v));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> (arr ? <span class="string">&quot;[&quot;</span> : <span class="string">&quot;&#123;&quot;</span>) + <span class="built_in">String</span>(json) + (arr ? <span class="string">&quot;]&quot;</span> : <span class="string">&quot;&#125;&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">jsonStringify(&#123;<span class="attr">x</span> : <span class="number">5</span>&#125;) <span class="comment">// &quot;&#123;&quot;x&quot;:5&#125;&quot;</span></span><br><span class="line">jsonStringify([<span class="number">1</span>, <span class="string">&quot;false&quot;</span>, <span class="literal">false</span>]) <span class="comment">// &quot;[1,&quot;false&quot;,false]&quot;</span></span><br><span class="line">jsonStringify(&#123;<span class="attr">b</span>: <span class="literal">undefined</span>&#125;) <span class="comment">// &quot;&#123;&quot;b&quot;:&quot;undefined&quot;&#125;&quot;</span></span><br></pre></td></tr></table></figure>

<h2 id="19-实现一个JSON-parse"><a href="#19-实现一个JSON-parse" class="headerlink" title="19.实现一个JSON.parse"></a>19.实现一个JSON.parse</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">JSON.parse(text[, reviver])</span><br></pre></td></tr></table></figure>

<blockquote>
<p>用来解析JSON字符串，构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)</p>
</blockquote>
<h3 id="方法1：直接调用-eval"><a href="#方法1：直接调用-eval" class="headerlink" title="方法1：直接调用 eval"></a>方法1：直接调用 eval</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">function jsonParse(opt) &#123;</span><br><span class="line">    return eval(&#39;(&#39; + opt + &#39;)&#39;);</span><br><span class="line">&#125;</span><br><span class="line">jsonParse(jsonStringify(&#123;x : 5&#125;))</span><br><span class="line">&#x2F;&#x2F; Object &#123; x: 5&#125;</span><br><span class="line">jsonParse(jsonStringify([1, &quot;false&quot;, false]))</span><br><span class="line">&#x2F;&#x2F; [1, &quot;false&quot;, falsr]</span><br><span class="line">jsonParse(jsonStringify(&#123;b: undefined&#125;))</span><br><span class="line">&#x2F;&#x2F; Object &#123; b: &quot;undefined&quot;&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>避免在不必要的情况下使用 <code>eval</code>，<code>eval()</code> 是一个危险的函数，他执行的代码拥有着执行者的权利。如果你用<code>eval()</code>运行的字符串代码被恶意方（不怀好意的人）操控修改，您最终可能会在您的网页/扩展程序的权限下，在用户计算机上运行恶意代码。它会执行JS代码，有XSS漏洞。</p>
</blockquote>
<p>如果你只想记这个方法，就得对参数json做校验。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> rx_one = <span class="regexp">/^[\],:&#123;&#125;\s]*$/</span>;</span><br><span class="line"><span class="keyword">var</span> rx_two = <span class="regexp">/\\(?:[&quot;\\\/bfnrt]|u[0-9a-fA-F]&#123;4&#125;)/g</span>;</span><br><span class="line"><span class="keyword">var</span> rx_three = <span class="regexp">/&quot;[^&quot;\\\n\r]*&quot;|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g</span>;</span><br><span class="line"><span class="keyword">var</span> rx_four = <span class="regexp">/(?:^|:|,)(?:\s*\[)+/g</span>;</span><br><span class="line"><span class="keyword">if</span> (</span><br><span class="line">    rx_one.test(</span><br><span class="line">        json</span><br><span class="line">            .replace(rx_two, <span class="string">&quot;@&quot;</span>)</span><br><span class="line">            .replace(rx_three, <span class="string">&quot;]&quot;</span>)</span><br><span class="line">            .replace(rx_four, <span class="string">&quot;&quot;</span>)</span><br><span class="line">    )</span><br><span class="line">) &#123;</span><br><span class="line">    <span class="keyword">var</span> obj = <span class="built_in">eval</span>(<span class="string">&quot;(&quot;</span> +json + <span class="string">&quot;)&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="方法2：Function"><a href="#方法2：Function" class="headerlink" title="方法2：Function"></a>方法2：Function</h3><blockquote>
<p>核心：Function与eval有相同的字符串参数特性</p>
</blockquote>
<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var func = new Function(arg1, arg2, ..., functionBody);</span><br></pre></td></tr></table></figure>

<p>在转换JSON的实际应用中，只需要这么做</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> jsonStr = <span class="string">&#x27;&#123; &quot;age&quot;: 20, &quot;name&quot;: &quot;jack&quot; &#125;&#x27;</span></span><br><span class="line"><span class="keyword">var</span> json = (<span class="keyword">new</span> <span class="built_in">Function</span>(<span class="string">&#x27;return &#x27;</span> + jsonStr))();</span><br></pre></td></tr></table></figure>

<blockquote>
<p><code>eval</code> 与 <code>Function</code>都有着动态编译js代码的作用，但是在实际的编程中并不推荐使用</p>
</blockquote>
<h2 id="20-解析-URL-Params-为对象"><a href="#20-解析-URL-Params-为对象" class="headerlink" title="20.解析 URL Params 为对象"></a>20.解析 URL Params 为对象</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> url = <span class="string">&#x27;http://www.domain.com/?user=anonymous&amp;id=123&amp;id=456&amp;city=%E5%8C%97%E4%BA%AC&amp;enabled&#x27;</span>;</span><br><span class="line">parseParam(url)</span><br><span class="line"><span class="comment">/* 结果</span></span><br><span class="line"><span class="comment">&#123; user: &#x27;anonymous&#x27;,</span></span><br><span class="line"><span class="comment">  id: [ 123, 456 ], // 重复出现的 key 要组装成数组，能被转成数字的就转成数字类型</span></span><br><span class="line"><span class="comment">  city: &#x27;北京&#x27;, // 中文需解码</span></span><br><span class="line"><span class="comment">  enabled: true, // 未指定值得 key 约定为 true</span></span><br><span class="line"><span class="comment">&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parseParam</span>(<span class="params">url</span>) </span>&#123;</span><br><span class="line"> 	<span class="comment">// 将 ? 后面的字符串取出来</span></span><br><span class="line">    <span class="keyword">const</span> paramsStr = <span class="regexp">/.+\?(.+)$/</span>.exec(url)[<span class="number">1</span>];</span><br><span class="line">    <span class="comment">// 将字符串以 &amp; 分割后存到数组中</span></span><br><span class="line">    <span class="keyword">const</span> paramsArr = paramsStr.split(<span class="string">&#x27;&amp;&#x27;</span>);</span><br><span class="line">    <span class="keyword">let</span> paramsObj = &#123;&#125;;</span><br><span class="line">    <span class="comment">// 将 params 存到对象中</span></span><br><span class="line">    paramsArr.forEach(<span class="function"><span class="params">param</span> =&gt;</span> &#123;</span><br><span class="line">     	<span class="comment">// 处理有 value 的参数</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="regexp">/=/</span>.test(param)) &#123;</span><br><span class="line">        	<span class="comment">// 分割 key 和 value</span></span><br><span class="line">        	<span class="keyword">let</span> [key, val] = param.split(<span class="string">&#x27;=&#x27;</span>);</span><br><span class="line">        	<span class="comment">// 递归调用解码</span></span><br><span class="line">        	val = <span class="built_in">decodeURIComponent</span>(val);</span><br><span class="line">        	<span class="comment">// 判断是否转为数字</span></span><br><span class="line">        	val = <span class="regexp">/^\d+$/</span>.test(val) ? <span class="built_in">parseFloat</span>(val) : val; </span><br><span class="line"></span><br><span class="line">			<span class="comment">// 如果对象有 key，则添加一个值</span></span><br><span class="line">            <span class="keyword">if</span> (paramsObj.hasOwnProperty(key)) &#123;</span><br><span class="line">            	paramsObj[key] = [].concat(paramsObj[key], val);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            	<span class="comment">// 如果对象没有这个 key，创建 key 并设置值</span></span><br><span class="line">            	paramsObj[key] = val;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        	<span class="comment">// 处理没有 value 的参数</span></span><br><span class="line">        	paramsObj[param] = <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    <span class="keyword">return</span> paramsObj;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="21-模板引擎实现"><a href="#21-模板引擎实现" class="headerlink" title="21.模板引擎实现"></a>21.模板引擎实现</h2><p>underscore 提供了模板引擎的功能，举个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">var tpl &#x3D; &quot;hello: &lt;%&#x3D; name %&gt;&quot;;</span><br><span class="line"></span><br><span class="line">var compiled &#x3D; _.template(tpl);</span><br><span class="line">compiled(&#123;name: &#39;Kevin&#39;&#125;); &#x2F;&#x2F; &quot;hello: Kevin&quot;</span><br></pre></td></tr></table></figure>

<p>感觉好像没有什么强大的地方，再来举个例子：</p>
<p>在 HTML 文件中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&lt;ul id&#x3D;&quot;name_list&quot;&gt;&lt;&#x2F;ul&gt;</span><br><span class="line"></span><br><span class="line">&lt;script type&#x3D;&quot;text&#x2F;html&quot; id&#x3D;&quot;user_tmpl&quot;&gt;</span><br><span class="line">    &lt;%for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; %&gt;</span><br><span class="line">        &lt;li&gt;</span><br><span class="line">            &lt;a href&#x3D;&quot;&lt;%&#x3D;users[i].url%&gt;&quot;&gt;</span><br><span class="line">                &lt;%&#x3D;users[i].name%&gt;</span><br><span class="line">            &lt;&#x2F;a&gt;</span><br><span class="line">        &lt;&#x2F;li&gt;</span><br><span class="line">    &lt;% &#125; %&gt;</span><br><span class="line">&lt;&#x2F;script&gt;</span><br></pre></td></tr></table></figure>

<p>JavaScript 文件中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">var container &#x3D; document.getElementById(&quot;name_list&quot;);</span><br><span class="line"></span><br><span class="line">var data &#x3D; &#123;</span><br><span class="line">    users: [</span><br><span class="line">        &#123; &quot;name&quot;: &quot;Kevin&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;,</span><br><span class="line">        &#123; &quot;name&quot;: &quot;Daisy&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;,</span><br><span class="line">        &#123; &quot;name&quot;: &quot;Kelly&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br><span class="line">var precompile &#x3D; _.template(document.getElementById(&quot;user_tmpl&quot;).innerHTML);</span><br><span class="line">var html &#x3D; precompile(data);</span><br><span class="line"></span><br><span class="line">container.innerHTML &#x3D; html;</span><br></pre></td></tr></table></figure>

<p>效果为：</p>
<p><img data-src="https://cdn.jsdelivr.net/gh/huxingyi1997/my_img/img/20210514224540.png" alt="template"></p>
<p>那么该如何实现这样一个 _.template 函数呢？</p>
<h3 id="实现思路-1"><a href="#实现思路-1" class="headerlink" title="实现思路"></a>实现思路</h3><p>underscore 的 template 函数参考了 jQuery 的作者 John Resig 在 2008 年发表的一篇文章 <a target="_blank" rel="noopener" href="https://johnresig.com/blog/javascript-micro-templating/#postcomment">JavaScript Micro-Templating</a>，我们先从这篇文章的思路出发，思考一下如何写一个简单的模板引擎。</p>
<p>依然是以这段模板字符串为例：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;%for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; %&gt;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&lt;%&#x3D;users[i].url%&gt;&quot;&gt;</span><br><span class="line">            &lt;%&#x3D;users[i].name%&gt;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&lt;% &#125; %&gt;</span><br></pre></td></tr></table></figure>

<p>John Resig 的思路是将这段代码转换为这样一段程序：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 模拟数据</span><br><span class="line">var users &#x3D; [&#123;&quot;name&quot;: &quot;Kevin&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot;&#125;];</span><br><span class="line"></span><br><span class="line">var p &#x3D; [];</span><br><span class="line">for (var i &#x3D; 0; i &lt; users.length; i++) &#123;</span><br><span class="line">    p.push(&#39;&lt;li&gt;&lt;a href&#x3D;&quot;&#39;);</span><br><span class="line">    p.push(users[i].url);</span><br><span class="line">    p.push(&#39;&quot;&gt;&#39;);</span><br><span class="line">    p.push(users[i].name);</span><br><span class="line">    p.push(&#39;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 最后 join 一下就可以得到最终拼接好的模板字符串</span><br><span class="line">console.log(p.join(&#39;&#39;)) &#x2F;&#x2F; &lt;li&gt;&lt;a href&#x3D;&quot;http:&#x2F;&#x2F;localhost&quot;&gt;Kevin&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;</span><br></pre></td></tr></table></figure>

<p>我们注意，模板其实是一段字符串，我们怎么根据一段字符串生成一段代码呢？很容易就想到用 eval，那我们就先用 eval 吧。</p>
<p>然后我们会发现，为了转换成这样一段代码，我们需要将<code>&lt;%xxx%&gt;</code>转换为 <code>xxx</code>，其实就是去掉包裹的符号，还要将 <code>&lt;%=xxx%&gt;</code>转化成 <code>p.push(xxx)</code>，这些都可以用正则实现，但是我们还需要写 <code>p.push(&#39;&lt;li&gt;&lt;a href=&quot;&#39;);</code> 、<code>p.push(&#39;&quot;&gt;&#39;);</code>呐，这些该如何实现呢？</p>
<p>那我们换个思路，依然是用正则，但是我们</p>
<ol>
<li>将 <code>%&gt;</code> 替换成 <code>p.push(&#39;</code></li>
<li>将 <code>&lt;%</code> 替换成 <code>&#39;);</code></li>
<li>将 <code>&lt;%=xxx%&gt;</code> 替换成 <code>&#39;);p.push(xxx);p.push(&#39;</code></li>
</ol>
<p>我们来举个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;%for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; %&gt;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&lt;%&#x3D;users[i].url%&gt;&quot;&gt;</span><br><span class="line">            &lt;%&#x3D;users[i].name%&gt;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&lt;% &#125; %&gt;</span><br></pre></td></tr></table></figure>

<p>按照这个替换规则会被替换为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#39;);for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; p.push(&#39;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&#39;);p.push(users[i].url);p.push(&#39;&quot;&gt;</span><br><span class="line">            &#39;);p.push(users[i].name);p.push(&#39;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&#39;); &#125; p.push(&#39;</span><br></pre></td></tr></table></figure>

<p>这样肯定会报错，毕竟代码都没有写全，我们在首和尾加上部分代码，变成：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 添加的首部代码</span><br><span class="line">var p &#x3D; []; p.push(&#39;</span><br><span class="line"></span><br><span class="line">&#39;);for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; p.push(&#39;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&#39;);p.push(users[i].url);p.push(&#39;&quot;&gt;</span><br><span class="line">            &#39;);p.push(users[i].name);p.push(&#39;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&#39;); &#125; p.push(&#39;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 添加的尾部代码</span><br><span class="line">&#39;);</span><br></pre></td></tr></table></figure>

<p>我们整理下这段代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var p &#x3D; []; p.push(&#39;&#39;);</span><br><span class="line">for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; </span><br><span class="line">    p.push(&#39;&lt;li&gt;&lt;a href&#x3D;&quot;&#39;);</span><br><span class="line">    p.push(users[i].url);</span><br><span class="line">    p.push(&#39;&quot;&gt;&#39;);</span><br><span class="line">    p.push(users[i].name);</span><br><span class="line">    p.push(&#39;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;); </span><br><span class="line">&#125;</span><br><span class="line">    p.push(&#39;&#39;);</span><br></pre></td></tr></table></figure>

<p>恰好可以实现这个功能，不过还要注意一点，要将换行符替换成空格，防止解析成代码的时候报错，不过在这里为了方便理解原理，就只在代码里实现。</p>
<h3 id="第一版-1"><a href="#第一版-1" class="headerlink" title="第一版"></a>第一版</h3><p>我们来尝试实现第一版：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第一版</span><br><span class="line">function tmpl(str, data) &#123;</span><br><span class="line">    var str &#x3D; document.getElementById(str).innerHTML;</span><br><span class="line"></span><br><span class="line">    var string &#x3D; &quot;var p &#x3D; []; p.push(&#39;&quot; +</span><br><span class="line">    str</span><br><span class="line">    .replace(&#x2F;[\r\t\n]&#x2F;g, &quot;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x3D;(.*?)%&gt;&#x2F;g, &quot;&#39;);p.push($1);p.push(&#39;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x2F;g, &quot;&#39;);&quot;)</span><br><span class="line">    .replace(&#x2F;%&gt;&#x2F;g,&quot;p.push(&#39;&quot;)</span><br><span class="line">    + &quot;&#39;);&quot;</span><br><span class="line"></span><br><span class="line">    eval(string)</span><br><span class="line"></span><br><span class="line">    return p.join(&#39;&#39;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>为了验证是否有用：</p>
<p>HTML 文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&lt;script type&#x3D;&quot;text&#x2F;html&quot; id&#x3D;&quot;user_tmpl&quot;&gt;</span><br><span class="line">    &lt;%for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; %&gt;</span><br><span class="line">        &lt;li&gt;</span><br><span class="line">            &lt;a href&#x3D;&quot;&lt;%&#x3D;users[i].url%&gt;&quot;&gt;</span><br><span class="line">                &lt;%&#x3D;users[i].name%&gt;</span><br><span class="line">            &lt;&#x2F;a&gt;</span><br><span class="line">        &lt;&#x2F;li&gt;</span><br><span class="line">    &lt;% &#125; %&gt;</span><br><span class="line">&lt;&#x2F;script&gt;</span><br></pre></td></tr></table></figure>

<p>JavaScript 文件：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">var users &#x3D; [</span><br><span class="line">    &#123; &quot;name&quot;: &quot;Byron&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;,</span><br><span class="line">    &#123; &quot;name&quot;: &quot;Casper&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;,</span><br><span class="line">    &#123; &quot;name&quot;: &quot;Frank&quot;, &quot;url&quot;: &quot;http:&#x2F;&#x2F;localhost&quot; &#125;</span><br><span class="line">]</span><br><span class="line">tmpl(&quot;user_tmpl&quot;, users)</span><br></pre></td></tr></table></figure>

<p>完整的 Demo 可以查看 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template1">template 示例一</a></p>
<h3 id="Function"><a href="#Function" class="headerlink" title="Function"></a>Function</h3><p>在这里我们使用了 eval ，实际上 John Resig 在文章中使用的是 Function 构造函数。</p>
<p>Function 构造函数创建一个新的 Function 对象。 在 JavaScript 中, 每个函数实际上都是一个 Function 对象。</p>
<p>使用方法为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">new Function ([arg1[, arg2[, ...argN]],] functionBody)</span><br></pre></td></tr></table></figure>

<p>arg1, arg2, … argN 表示函数用到的参数，functionBody 表示一个含有包括函数定义的 JavaScript 语句的字符串。</p>
<p>举个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var adder &#x3D; new Function(&quot;a&quot;, &quot;b&quot;, &quot;return a + b&quot;);</span><br><span class="line"></span><br><span class="line">adder(2, 6); &#x2F;&#x2F; 8</span><br></pre></td></tr></table></figure>

<p>那么 John Resig 到底是如何实现的呢？</p>
<h3 id="第二版"><a href="#第二版" class="headerlink" title="第二版"></a>第二版</h3><p>使用 Function 构造函数：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第二版</span><br><span class="line">function tmpl(str, data) &#123;</span><br><span class="line">    var str &#x3D; document.getElementById(str).innerHTML;</span><br><span class="line"></span><br><span class="line">    var fn &#x3D; new Function(&quot;obj&quot;,</span><br><span class="line"></span><br><span class="line">    &quot;var p &#x3D; []; p.push(&#39;&quot; +</span><br><span class="line"></span><br><span class="line">    str</span><br><span class="line">    .replace(&#x2F;[\r\t\n]&#x2F;g, &quot;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x3D;(.*?)%&gt;&#x2F;g, &quot;&#39;);p.push($1);p.push(&#39;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x2F;g, &quot;&#39;);&quot;)</span><br><span class="line">    .replace(&#x2F;%&gt;&#x2F;g,&quot;p.push(&#39;&quot;)</span><br><span class="line">    + &quot;&#39;);return p.join(&#39;&#39;);&quot;);</span><br><span class="line"></span><br><span class="line">    return fn(data);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>使用方法依然跟第一版相同，具体 Demo 可以查看 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template2">template 示例二</a></p>
<p>不过值得注意的是：其实 tmpl 函数没有必要传入 data 参数，也没有必要在最后 return 的时候，传入 data 参数，即使你把这两个参数都去掉，代码还是可以正常执行的。</p>
<p>这是因为:</p>
<blockquote>
<p>使用Function构造器生成的函数，并不会在创建它们的上下文中创建闭包；它们一般在全局作用域中被创建。当运行这些函数的时候，它们只能访问自己的本地变量和全局变量，不能访问Function构造器被调用生成的上下文的作用域。这和使用带有函数表达式代码的 eval 不同。</p>
</blockquote>
<p>这里之所以依然传入了 data 参数，是为了下一版做准备。</p>
<h3 id="with"><a href="#with" class="headerlink" title="with"></a>with</h3><p>现在有一个小问题，就是实际上我们传入的数据结构可能比较复杂，比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">var data &#x3D; &#123;</span><br><span class="line">    status: 200,</span><br><span class="line">    name: &#39;kevin&#39;,</span><br><span class="line">    friends: [...]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果我们将这个数据结构传入 tmpl 函数中，在模板字符串中，如果要用到某个数据，总是需要使用 <code>data.name</code>、<code>data.friends</code> 的形式来获取，麻烦就麻烦在我想直接使用 name、friends 等变量，而不是繁琐的使用 <code>data.</code> 来获取。</p>
<p>这又该如何实现的呢？答案是 with。</p>
<p>with 语句可以扩展一个语句的作用域链(scope chain)。当需要多次访问一个对象的时候，可以使用 with 做简化。比如：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">var hostName &#x3D; location.hostname;</span><br><span class="line">var url &#x3D; location.href;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 使用 with</span><br><span class="line">with(location)&#123;</span><br><span class="line">    var hostname &#x3D; hostname;</span><br><span class="line">    var url &#x3D; href;</span><br><span class="line">&#125;</span><br><span class="line">function Person()&#123;</span><br><span class="line">    this.name &#x3D; &#39;Kevin&#39;;</span><br><span class="line">    this.age &#x3D; &#39;18&#39;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">var person &#x3D; new Person();</span><br><span class="line"></span><br><span class="line">with(person) &#123;</span><br><span class="line">    console.log(&#39;my name is &#39; + name + &#39;, age is &#39; + age + &#39;.&#39;)</span><br><span class="line">&#125;</span><br><span class="line">&#x2F;&#x2F; my name is Kevin, age is 18.</span><br></pre></td></tr></table></figure>

<p>最后：不建议使用 with 语句，因为它可能是混淆错误和兼容性问题的根源，除此之外，也会造成性能低下</p>
<h3 id="第三版"><a href="#第三版" class="headerlink" title="第三版"></a>第三版</h3><p>使用 with ，我们再写一版代码：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第三版</span><br><span class="line">function tmpl(str, data) &#123;</span><br><span class="line">    var str &#x3D; document.getElementById(str).innerHTML;</span><br><span class="line"></span><br><span class="line">    var fn &#x3D; new Function(&quot;obj&quot;,</span><br><span class="line"></span><br><span class="line">    &#x2F;&#x2F; 其实就是这里多添加了一句 with(obj)&#123;...&#125;</span><br><span class="line">    &quot;var p &#x3D; []; with(obj)&#123;p.push(&#39;&quot; +</span><br><span class="line"></span><br><span class="line">    str</span><br><span class="line">    .replace(&#x2F;[\r\t\n]&#x2F;g, &quot;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x3D;(.*?)%&gt;&#x2F;g, &quot;&#39;);p.push($1);p.push(&#39;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x2F;g, &quot;&#39;);&quot;)</span><br><span class="line">    .replace(&#x2F;%&gt;&#x2F;g,&quot;p.push(&#39;&quot;)</span><br><span class="line">    + &quot;&#39;);&#125;return p.join(&#39;&#39;);&quot;);</span><br><span class="line"></span><br><span class="line">    return fn(data);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>具体 Demo 可以查看 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template3">template 示例三</a></p>
<h3 id="第四版"><a href="#第四版" class="headerlink" title="第四版"></a>第四版</h3><p>如果我们的模板不变，数据却发生了变化，如果使用我们的之前写的 tmpl 函数，每次都会 new Function，这其实是没有必要的，如果我们能在使用 tmpl 的时候，返回一个函数，然后使用该函数，传入不同的数据，只根据数据不同渲染不同的 html 字符串，就可以避免这种无谓的损失。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第四版</span><br><span class="line">function tmpl(str, data) &#123;</span><br><span class="line">    var str &#x3D; document.getElementById(str).innerHTML;</span><br><span class="line">    var fn &#x3D; new Function(&quot;obj&quot;,</span><br><span class="line">    &quot;var p &#x3D; []; with(obj)&#123;p.push(&#39;&quot; +</span><br><span class="line">    str</span><br><span class="line">    .replace(&#x2F;[\r\t\n]&#x2F;g, &quot;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x3D;(.*?)%&gt;&#x2F;g, &quot;&#39;);p.push($1);p.push(&#39;&quot;)</span><br><span class="line">    .replace(&#x2F;&lt;%&#x2F;g, &quot;&#39;);&quot;)</span><br><span class="line">    .replace(&#x2F;%&gt;&#x2F;g,&quot;p.push(&#39;&quot;)</span><br><span class="line">    + &quot;&#39;);&#125;return p.join(&#39;&#39;);&quot;);</span><br><span class="line"></span><br><span class="line">    var template &#x3D; function(data) &#123;</span><br><span class="line">        return fn.call(this, data)</span><br><span class="line">    &#125;</span><br><span class="line">    return template;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 使用时</span><br><span class="line">var compiled &#x3D; tmpl(&quot;user_tmpl&quot;);</span><br><span class="line">results.innerHTML &#x3D; compiled(data);</span><br></pre></td></tr></table></figure>

<p>具体 Demo 可以查看 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template4">template 示例四</a></p>
<h3 id="反斜杠的作用"><a href="#反斜杠的作用" class="headerlink" title="反斜杠的作用"></a>反斜杠的作用</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var txt &#x3D; &quot;We are the so-called &quot;Vikings&quot; from the north.&quot;</span><br><span class="line">console.log(txt);</span><br></pre></td></tr></table></figure>

<p>我们的本意是想打印带 <code>&quot;&quot;</code> 包裹的 <code>Vikings</code> 字符串，但是在 JavaScript 中，字符串使用单引号或者双引号来表示起始或者结束，这段代码会报 <code>Unexpected identifier</code> 错误。</p>
<p>如果我们就是想要在字符串中使用单引号或者双引号呢？</p>
<p>我们可以使用反斜杠用来在文本字符串中插入省略号、换行符、引号和其他特殊字符：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var txt &#x3D; &quot;We are the so-called \&quot;Vikings\&quot; from the north.&quot;</span><br><span class="line">console.log(txt);</span><br></pre></td></tr></table></figure>

<p>现在 JavaScript 就可以输出正确的文本字符串了。</p>
<p><strong>这种由反斜杠后接字母或数字组合构成的字符组合就叫做“转义序列”。</strong></p>
<p>值得注意的是，转义序列会被视为单个字符。</p>
<p>我们常见的转义序列还有 <code>\n</code> 表示换行、<code>\t</code> 表示制表符、<code>\r</code> 表示回车等等。</p>
<h3 id="转义序列"><a href="#转义序列" class="headerlink" title="转义序列"></a>转义序列</h3><p>在 JavaScript 中，字符串值是一个由零或多个 Unicode 字符（字母、数字和其他字符）组成的序列。</p>
<p>字符串中的每个字符均可由一个转义序列表示。比如字母 <code>a</code>，也可以用转义序列 <code>\u0061</code> 表示。</p>
<blockquote>
<p>转义序列以反斜杠 <code>\</code> 开头，它的作用是告知 JavaScript 解释器下一个字符是特殊字符。</p>
</blockquote>
<blockquote>
<p>转义序列的语法为 <code>\uhhhh</code>，其中 hhhh 是四位十六进制数。</p>
</blockquote>
<p>根据这个规则，我们可以算出常见字符的转义序列，以字母 <code>m</code> 为例：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 1. 求出字符 &#96;m&#96; 对应的 unicode 值</span><br><span class="line">var unicode &#x3D; &#39;m&#39;.charCodeAt(0) &#x2F;&#x2F; 109</span><br><span class="line">&#x2F;&#x2F; 2. 转成十六进制</span><br><span class="line">var result &#x3D; unicode.toString(16); &#x2F;&#x2F; &quot;6d&quot;</span><br></pre></td></tr></table></figure>

<p>我们就可以使用 <code>\u006d</code> 表示 <code>m</code>，不信你可以直接在浏览器命令行中直接输入字符串 <code>&#39;\u006d&#39;</code>，看下打印结果。</p>
<p>值得注意的是: <code>\n</code> 虽然也是一种转义序列，但是也可以使用上面的方式：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var unicode &#x3D; &#39;\n&#39;.charCodeAt(0) &#x2F;&#x2F; 10</span><br><span class="line">var result &#x3D; unicode.toString(16); &#x2F;&#x2F; &quot;a&quot;</span><br></pre></td></tr></table></figure>

<p>所以我们可以用 <code>\u000A</code> 来表示换行符 <code>\n</code>，比如在浏览器命令行中直接输入 <code>&#39;a \n b&#39;</code> 和 <code>&#39;a \u000A b&#39;</code> 效果是一样的。</p>
<p>讲了这么多，我们来看看一些常用字符的转义序列以及含义：</p>
<table>
<thead>
<tr>
<th>Unicode 字符值</th>
<th>转义序列</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>\u0009</td>
<td>\t</td>
<td>制表符</td>
</tr>
<tr>
<td>\u000A</td>
<td>\n</td>
<td>换行</td>
</tr>
<tr>
<td>\u000D</td>
<td>\r</td>
<td>回车</td>
</tr>
<tr>
<td>\u0022</td>
<td>&quot;</td>
<td>双引号</td>
</tr>
<tr>
<td>\u0027</td>
<td>&#39;</td>
<td>单引号</td>
</tr>
<tr>
<td>\u005C</td>
<td>\</td>
<td>反斜杠</td>
</tr>
<tr>
<td>\u2028</td>
<td></td>
<td>行分隔符</td>
</tr>
<tr>
<td>\u2029</td>
<td></td>
<td>段落分隔符</td>
</tr>
</tbody></table>
<h3 id="Line-Terminators"><a href="#Line-Terminators" class="headerlink" title="Line Terminators"></a>Line Terminators</h3><p>Line Terminators，中文译文<code>行终结符</code>。像空白字符一样，<code>行终结符</code>可用于改善源文本的可读性。</p>
<p>在 ES5 中，有四个字符被认为是<code>行终结符</code>，其他的折行字符都会被视为空白。</p>
<p>这四个字符如下所示：</p>
<table>
<thead>
<tr>
<th>字符编码值</th>
<th>名称</th>
</tr>
</thead>
<tbody><tr>
<td>\u000A</td>
<td>换行符</td>
</tr>
<tr>
<td>\u000D</td>
<td>回车符</td>
</tr>
<tr>
<td>\u2028</td>
<td>行分隔符</td>
</tr>
<tr>
<td>\u2029</td>
<td>段落分隔符</td>
</tr>
</tbody></table>
<h3 id="Function-1"><a href="#Function-1" class="headerlink" title="Function"></a>Function</h3><p>试想我们写这样一段代码，能否正确运行：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var log &#x3D; new Function(&quot;var a &#x3D; &#39;1\t23&#39;;console.log(a)&quot;);</span><br><span class="line">log()</span><br></pre></td></tr></table></figure>

<p>答案是可以，那下面这段呢：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var log &#x3D; new Function(&quot;var a &#x3D; &#39;1\n23&#39;;console.log(a)&quot;);</span><br><span class="line">log()</span><br></pre></td></tr></table></figure>

<p>答案是不可以，会报错 <code>Uncaught SyntaxError: Invalid or unexpected token</code>。</p>
<p>这是为什么呢？</p>
<p>这是因为在 Function 构造函数的实现中，首先会将函数体代码字符串进行一次 <code>ToString</code> 操作，这时候字符串变成了：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var a &#x3D; &#39;1</span><br><span class="line">23&#39;;console.log(a)</span><br></pre></td></tr></table></figure>

<p>然后再检测代码字符串是否符合代码规范，在 JavaScript 中，<strong>字符串表达式中是不允许换行的</strong>，这就导致了报错。</p>
<p>为了避免这个问题，我们需要将代码修改为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var log &#x3D; new Function(&quot;var a &#x3D; &#39;1\\n23&#39;;console.log(a)&quot;);</span><br><span class="line">log()</span><br></pre></td></tr></table></figure>

<p>其实不止 <code>\n</code>，其他三种 <code>行终结符</code>，如果你在字符串表达式中直接使用，都会导致报错！</p>
<p>之所以讲这个问题，是因为在模板引擎的实现中，就是使用了 Function 构造函数，如果我们在模板字符串中使用了 <code>行终结符</code>，便有可能会出现一样的错误，所以我们必须要对这四种 <code>行终结符</code> 进行特殊的处理。</p>
<h3 id="特殊字符"><a href="#特殊字符" class="headerlink" title="特殊字符"></a>特殊字符</h3><p>除了这四种 <code>行终结符</code> 之外，我们还要对两个字符进行处理。</p>
<p>一个是 <code>\</code>。</p>
<p>比如说我们的模板内容中使用了<code>\</code>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var log &#x3D; new Function(&quot;var a &#x3D; &#39;1\23&#39;;console.log(a)&quot;);</span><br><span class="line">log(); &#x2F;&#x2F; 1</span><br></pre></td></tr></table></figure>

<p>其实我们是想打印 ‘1\23’，但是因为把 <code>\</code> 当成了特殊字符的标记进行处理，所以最终打印了 1。</p>
<p>同样的道理，如果我们在使用模板引擎的时候，使用了 <code>\</code> 字符串，也会导致错误的处理。</p>
<p>第二个是 <code>&#39;</code>。</p>
<p>如果我们在模板引擎中使用了 <code>&#39;</code>，因为我们会拼接诸如 <code>p.push(&#39;</code> <code>&#39;)</code> 等字符串，因为 <code>&#39;</code> 的原因，字符串会被错误拼接，也会导致错误。</p>
<p>所以总共我们需要对六种字符进行特殊处理，处理的方式，就是正则匹配出这些特殊字符，然后比如将 <code>\n</code> 替换成 <code>\\n</code>，<code>\</code> 替换成 <code>\\</code>，<code>&#39;</code> 替换成 <code>\\&#39;</code>，处理的代码为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var escapes &#x3D; &#123;</span><br><span class="line">    &quot;&#39;&quot;: &quot;&#39;&quot;,</span><br><span class="line">    &#39;\\&#39;: &#39;\\&#39;,</span><br><span class="line">    &#39;\r&#39;: &#39;r&#39;,</span><br><span class="line">    &#39;\n&#39;: &#39;n&#39;,</span><br><span class="line">    &#39;\u2028&#39;: &#39;u2028&#39;,</span><br><span class="line">    &#39;\u2029&#39;: &#39;u2029&#39;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var escapeRegExp &#x3D; &#x2F;\\|&#39;|\r|\n|\u2028|\u2029&#x2F;g;</span><br><span class="line"></span><br><span class="line">var escapeChar &#x3D; function(match) &#123;</span><br><span class="line">    return &#39;\\&#39; + escapes[match];</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>我们测试一下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;console.log(&quot;I am \n Kevin&quot;);&#39;;</span><br><span class="line">var newStr &#x3D; str.replace(escapeRegExp, escapeChar);</span><br><span class="line"></span><br><span class="line">eval(newStr)</span><br><span class="line">&#x2F;&#x2F; I am </span><br><span class="line">&#x2F;&#x2F; Kevin</span><br></pre></td></tr></table></figure>

<h3 id="replace"><a href="#replace" class="headerlink" title="replace"></a>replace</h3><p>我们来讲一讲字符串的 replace 函数：</p>
<p>语法为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">str.replace(regexp|substr, newSubStr|function)</span><br></pre></td></tr></table></figure>

<p>replace 的第一个参数，可以传一个字符串，也可以传一个正则表达式。</p>
<p>第二个参数，可以传一个新字符串，也可以传一个函数。</p>
<p>我们重点看下传入函数的情况，简单举一个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;hello world&#39;;</span><br><span class="line">var newStr &#x3D; str.replace(&#39;world&#39;, function(match)&#123;</span><br><span class="line">    return match + &#39;!&#39;</span><br><span class="line">&#125;)</span><br><span class="line">console.log(newStr); &#x2F;&#x2F; hello world!</span><br></pre></td></tr></table></figure>

<p>match 表示匹配到的字符串，但函数的参数其实不止有 match，我们看个更复杂的例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">function replacer(match, p1, p2, p3, offset, string) &#123;</span><br><span class="line">    &#x2F;&#x2F; match，表示匹配的子串 abc12345#$*%</span><br><span class="line">    &#x2F;&#x2F; p1，第 1 个括号匹配的字符串 abc</span><br><span class="line">    &#x2F;&#x2F; p2，第 2 个括号匹配的字符串 12345</span><br><span class="line">    &#x2F;&#x2F; p3，第 3 个括号匹配的字符串 #$*%</span><br><span class="line">    &#x2F;&#x2F; offset，匹配到的子字符串在原字符串中的偏移量 0</span><br><span class="line">    &#x2F;&#x2F; string，被匹配的原字符串 abc12345#$*%</span><br><span class="line">    return [p1, p2, p3].join(&#39; - &#39;);</span><br><span class="line">&#125;</span><br><span class="line">var newString &#x3D; &#39;abc12345#$*%&#39;.replace(&#x2F;([^\d]*)(\d*)([^\w]*)&#x2F;, replacer); &#x2F;&#x2F; abc - 12345 - #$*%</span><br></pre></td></tr></table></figure>

<p>另外要注意的是，如果第一个参数是正则表达式，并且其为全局匹配模式， 那么这个方法将被多次调用，每次匹配都会被调用。</p>
<p>举个例子，如果我们要在一段字符串中匹配出 <code>&lt;%=xxx%&gt;</code> 中的值：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;li&gt;&lt;a href&#x3D;&quot;&lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;(.+?)%&gt;&#x2F;g, function(match, p1, offset, string)&#123;</span><br><span class="line">    console.log(match);</span><br><span class="line">    console.log(p1);</span><br><span class="line">    console.log(offset);</span><br><span class="line">    console.log(string);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>传入的函数会被执行两次，第一次的打印结果为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;%&#x3D;www.baidu.com%&gt;</span><br><span class="line">www.baidu.com</span><br><span class="line">13</span><br><span class="line">&lt;li&gt;&lt;a href&#x3D;&quot;&lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;</span><br></pre></td></tr></table></figure>

<p>第二次的打印结果为：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;%&#x3D;baidu%&gt;</span><br><span class="line">&#39;baidu&#39;</span><br><span class="line">33</span><br><span class="line">&lt;li&gt;&lt;a href&#x3D;&quot;&lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;</span><br></pre></td></tr></table></figure>

<h3 id="正则表达式的创建"><a href="#正则表达式的创建" class="headerlink" title="正则表达式的创建"></a>正则表达式的创建</h3><p>当我们要建立一个正则表达式的时候，我们可以直接创建：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var reg &#x3D; &#x2F;ab+c&#x2F;i;</span><br></pre></td></tr></table></figure>

<p>也可以使用构造函数的方式：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">new RegExp(&#39;ab+c&#39;, &#39;i&#39;);</span><br></pre></td></tr></table></figure>

<p>值得一提的是：每个正则表达式对象都有一个 source 属性，返回当前正则表达式对象的模式文本的字符串：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">var regex &#x3D; &#x2F;fooBar&#x2F;ig;</span><br><span class="line">console.log(regex.source); &#x2F;&#x2F; &quot;fooBar&quot;，不包含 &#x2F;...&#x2F; 和 &quot;ig&quot;。</span><br></pre></td></tr></table></figure>

<h3 id="正则表达式的特殊字符"><a href="#正则表达式的特殊字符" class="headerlink" title="正则表达式的特殊字符"></a>正则表达式的特殊字符</h3><p>正则表达式中有一些特殊字符，比如 <code>\d</code> 就表示了匹配一个数字，等价于 [0-9]。</p>
<p>在上节，我们使用 <code>/&lt;%=(.+?)%&gt;/g</code> 来匹配 <code>&lt;%=xxx%&gt;</code>，然而在 underscore 的实现中，用的却是 <code>/&lt;%=([\s\S]+?)%&gt;/g</code>。</p>
<p>我们知道 \s 表示匹配一个空白符，包括空格、制表符、换页符、换行符和其他 Unicode 空格，\S<br>匹配一个非空白符，[\s\S]就表示匹配所有的内容，可是为什么我们不直接使用 <code>.</code> 呢？</p>
<p>我们可能以为 <code>.</code> 匹配任意单个字符，实际上，并不是如此， <code>.</code>匹配除<code>行终结符</code>之外的任何单个字符，不信我们做个试验：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;%&#x3D;hello world%&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;(.+?)%&gt;&#x2F;g, function(match)&#123;</span><br><span class="line">    console.log(match); &#x2F;&#x2F; &lt;%&#x3D;hello world%&gt;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>但是如果我们在 hello world 之间加上一个<code>行终结符</code>，比如说 ‘\u2029’：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;%&#x3D;hello \u2029 world%&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;(.+?)%&gt;&#x2F;g, function(match)&#123;</span><br><span class="line">    console.log(match);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>因为匹配不到，所以也不会执行 console.log 函数。</p>
<p>但是改成 <code>/&lt;%=([\s\S]+?)%&gt;/g</code> 就可以正常匹配：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;%&#x3D;hello \u2029 world%&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;([\s\S]+?)%&gt;&#x2F;g, function(match)&#123;</span><br><span class="line">    console.log(match); &#x2F;&#x2F; &lt;%&#x3D;hello   world%&gt;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h3 id="惰性匹配"><a href="#惰性匹配" class="headerlink" title="惰性匹配"></a>惰性匹配</h3><p>仔细看 <code>/&lt;%=([\s\S]+?)%&gt;/g</code> 这个正则表达式，我们知道 <code>x+</code> 表示匹配 <code>x</code> 1 次或多次。<code>x?</code>表示匹配 <code>x</code> 0 次或 1 次，但是 <code>+?</code> 是个什么鬼？</p>
<p>实际上，如果在数量词 *、+、? 或 {}, 任意一个后面紧跟该符号（?），会使数量词变为非贪婪（ non-greedy） ，即匹配次数最小化。反之，默认情况下，是贪婪的（greedy），即匹配次数最大化。</p>
<p>举个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">console.log(&quot;aaabc&quot;.replace(&#x2F;a+&#x2F;g, &quot;d&quot;)); &#x2F;&#x2F; dbc</span><br><span class="line"></span><br><span class="line">console.log(&quot;aaabc&quot;.replace(&#x2F;a+?&#x2F;g, &quot;d&quot;)); &#x2F;&#x2F; dddbc</span><br></pre></td></tr></table></figure>

<p>在这里我们应该使用非惰性匹配，举个例子：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;li&gt;&lt;a href&#x3D;&quot;&lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;(.+?)%&gt;&#x2F;g, function(match)&#123;</span><br><span class="line">    console.log(match);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; &lt;%&#x3D;www.baidu.com%&gt;</span><br><span class="line">&#x2F;&#x2F; &lt;%&#x3D;baidu%&gt;</span><br></pre></td></tr></table></figure>

<p>如果我们使用惰性匹配：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &#39;&lt;li&gt;&lt;a href&#x3D;&quot;&lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;</span><br><span class="line"></span><br><span class="line">str.replace(&#x2F;&lt;%&#x3D;(.+)%&gt;&#x2F;g, function(match)&#123;</span><br><span class="line">    console.log(match);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; &lt;%&#x3D;www.baidu.com%&gt;&quot;&gt;&lt;%&#x3D;baidu%&gt;</span><br></pre></td></tr></table></figure>

<h3 id="template"><a href="#template" class="headerlink" title="template"></a>template</h3><p>讲完需要的知识点，我们开始讲 underscore 模板引擎的实现。</p>
<p>与我们上篇使用数组的 push ，最后再 join 的方法不同，underscore 使用的是字符串拼接的方式。</p>
<p>比如下面这样一段模板字符串：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&lt;%for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; %&gt;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&lt;%&#x3D;users[i].url%&gt;&quot;&gt;</span><br><span class="line">            &lt;%&#x3D;users[i].name%&gt;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&lt;% &#125; %&gt;</span><br></pre></td></tr></table></figure>

<p>我们先将 <code>&lt;%=xxx%&gt;</code> 替换成 <code>&#39;+ xxx +&#39;</code>，再将 <code>&lt;%xxx%&gt;</code> 替换成 <code>&#39;; xxx __p+=&#39;</code>:</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">&#39;;for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; __p+&#x3D;&#39;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&#39;+ users[i].url + &#39;&quot;&gt;</span><br><span class="line">            &#39;+ users[i].name +&#39;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&#39;;  &#125; __p+&#x3D;&#39;</span><br></pre></td></tr></table></figure>

<p>这段代码肯定会运行错误的，所以我们再添加些头尾代码，然后组成一个完整的代码字符串：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">var __p&#x3D;&#39;&#39;;</span><br><span class="line">with(obj)&#123;</span><br><span class="line">__p+&#x3D;&#39;</span><br><span class="line"></span><br><span class="line">&#39;;for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; __p+&#x3D;&#39;</span><br><span class="line">    &lt;li&gt;</span><br><span class="line">        &lt;a href&#x3D;&quot;&#39;+ users[i].url + &#39;&quot;&gt;</span><br><span class="line">            &#39;+ users[i].name +&#39;</span><br><span class="line">        &lt;&#x2F;a&gt;</span><br><span class="line">    &lt;&#x2F;li&gt;</span><br><span class="line">&#39;;  &#125; __p+&#x3D;&#39;</span><br><span class="line"></span><br><span class="line">&#39;;</span><br><span class="line">&#125;;</span><br><span class="line">return __p;</span><br></pre></td></tr></table></figure>

<p>整理下代码就是：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">var __p&#x3D;&#39;&#39;;</span><br><span class="line">with(obj)&#123;</span><br><span class="line">    __p+&#x3D;&#39;&#39;;</span><br><span class="line">    for ( var i &#x3D; 0; i &lt; users.length; i++ ) &#123; </span><br><span class="line">        __p+&#x3D;&#39;&lt;li&gt;&lt;a href&#x3D;&quot;&#39;+ users[i].url + &#39;&quot;&gt; &#39;+ users[i].name +&#39;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;&#39;;</span><br><span class="line">    &#125;</span><br><span class="line">    __p+&#x3D;&#39;&#39;;</span><br><span class="line">&#125;;</span><br><span class="line">return __p</span><br></pre></td></tr></table></figure>

<p>然后我们将 <code>__p</code> 这段代码字符串传入 Function 构造函数中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">var render &#x3D; new Function(data, __p)</span><br></pre></td></tr></table></figure>

<p>我们执行这个 render 函数，传入需要的 data 数据，就可以返回一段 HTML 字符串：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">render(data)</span><br></pre></td></tr></table></figure>

<h3 id="第五版-特殊字符的处理"><a href="#第五版-特殊字符的处理" class="headerlink" title="第五版 - 特殊字符的处理"></a>第五版 - 特殊字符的处理</h3><p>我们接着上篇的<a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template4">第四版</a>进行书写，不过加入对特殊字符的转义以及使用字符串拼接的方式：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 第五版</span><br><span class="line">var settings &#x3D; &#123;</span><br><span class="line">    &#x2F;&#x2F; 求值</span><br><span class="line">    evaluate: &#x2F;&lt;%([\s\S]+?)%&gt;&#x2F;g,</span><br><span class="line">    &#x2F;&#x2F; 插入</span><br><span class="line">    interpolate: &#x2F;&lt;%&#x3D;([\s\S]+?)%&gt;&#x2F;g,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var escapes &#x3D; &#123;</span><br><span class="line">    &quot;&#39;&quot;: &quot;&#39;&quot;,</span><br><span class="line">    &#39;\\&#39;: &#39;\\&#39;,</span><br><span class="line">    &#39;\r&#39;: &#39;r&#39;,</span><br><span class="line">    &#39;\n&#39;: &#39;n&#39;,</span><br><span class="line">    &#39;\u2028&#39;: &#39;u2028&#39;,</span><br><span class="line">    &#39;\u2029&#39;: &#39;u2029&#39;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var escapeRegExp &#x3D; &#x2F;\\|&#39;|\r|\n|\u2028|\u2029&#x2F;g;</span><br><span class="line"></span><br><span class="line">var template &#x3D; function(text) &#123;</span><br><span class="line"></span><br><span class="line">    var source &#x3D; &quot;var __p&#x3D;&#39;&#39;;\n&quot;;</span><br><span class="line">    source &#x3D; source + &quot;with(obj)&#123;\n&quot;</span><br><span class="line">    source &#x3D; source + &quot;__p+&#x3D;&#39;&quot;;</span><br><span class="line"></span><br><span class="line">    var main &#x3D; text</span><br><span class="line">    .replace(escapeRegExp, function(match) &#123;</span><br><span class="line">        return &#39;\\&#39; + escapes[match];</span><br><span class="line">    &#125;)</span><br><span class="line">    .replace(settings.interpolate, function(match, interpolate)&#123;</span><br><span class="line">        return &quot;&#39;+\n&quot; + interpolate + &quot;+\n&#39;&quot;</span><br><span class="line">    &#125;)</span><br><span class="line">    .replace(settings.evaluate, function(match, evaluate)&#123;</span><br><span class="line">        return &quot;&#39;;\n &quot; + evaluate + &quot;\n__p+&#x3D;&#39;&quot;</span><br><span class="line">    &#125;)</span><br><span class="line"></span><br><span class="line">    source &#x3D; source + main + &quot;&#39;;\n &#125;; \n return __p;&quot;;</span><br><span class="line"></span><br><span class="line">    console.log(source)</span><br><span class="line"></span><br><span class="line">    var render &#x3D; new Function(&#39;obj&#39;,  source);</span><br><span class="line"></span><br><span class="line">    return render;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>完整的使用代码可以参考 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template5">template 示例五</a>。</p>
<h3 id="第六版-特殊值的处理"><a href="#第六版-特殊值的处理" class="headerlink" title="第六版 - 特殊值的处理"></a>第六版 - 特殊值的处理</h3><p>不过有一点需要注意的是：</p>
<p>如果数据中 <code>users[i].url</code> 不存在怎么办？此时取值的结果为 undefined，我们知道：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#39;1&#39; + undefined &#x2F;&#x2F; &quot;1undefined&quot;</span><br></pre></td></tr></table></figure>

<p>就相当于拼接了 undefined 字符串，这肯定不是我们想要的。我们可以在代码中加入一点判断：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">.replace(settings.interpolate, function(match, interpolate)&#123;</span><br><span class="line">    return &quot;&#39;+\n&quot; + (interpolate &#x3D;&#x3D; null ? &#39;&#39; : interpolate) + &quot;+\n&#39;&quot;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>但是吧，我就是不喜欢写两遍 interpolate …… 嗯？那就这样吧：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">var source &#x3D; &quot;var __t, __p&#x3D;&#39;&#39;;\n&quot;;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line">.replace(settings.interpolate, function(match, interpolate)&#123;</span><br><span class="line">    return &quot;&#39;+\n((__t&#x3D;(&quot; + interpolate + &quot;))&#x3D;&#x3D;null?&#39;&#39;:__t)+\n&#39;&quot;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>其实就相当于：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var __t;</span><br><span class="line"></span><br><span class="line">var result &#x3D; (__t &#x3D; interpolate) &#x3D;&#x3D; null ? &#39;&#39; : __t;</span><br></pre></td></tr></table></figure>

<p>完整的使用代码可以参考 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template6">template 示例六</a>。</p>
<h3 id="第七版"><a href="#第七版" class="headerlink" title="第七版"></a>第七版</h3><p>现在我们使用的方式是将模板字符串进行多次替换，然而在 underscore 的实现中，只进行了一次替换，我们来看看 underscore 是怎么实现的：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">var template &#x3D; function(text) &#123;</span><br><span class="line">    var matcher &#x3D; RegExp([</span><br><span class="line">        (settings.interpolate).source,</span><br><span class="line">        (settings.evaluate).source</span><br><span class="line">    ].join(&#39;|&#39;) + &#39;|$&#39;, &#39;g&#39;);</span><br><span class="line"></span><br><span class="line">    var index &#x3D; 0;</span><br><span class="line">    var source &#x3D; &quot;__p+&#x3D;&#39;&quot;;</span><br><span class="line"></span><br><span class="line">    text.replace(matcher, function(match, interpolate, evaluate, offset) &#123;</span><br><span class="line">        source +&#x3D; text.slice(index, offset).replace(escapeRegExp, function(match) &#123;</span><br><span class="line">            return &#39;\\&#39; + escapes[match];</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        index &#x3D; offset + match.length;</span><br><span class="line"></span><br><span class="line">        if (interpolate) &#123;</span><br><span class="line">            source +&#x3D; &quot;&#39;+\n((__t&#x3D;(&quot; + interpolate + &quot;))&#x3D;&#x3D;null?&#39;&#39;:__t)+\n&#39;&quot;;</span><br><span class="line">        &#125; else if (evaluate) &#123;</span><br><span class="line">            source +&#x3D; &quot;&#39;;\n&quot; + evaluate + &quot;\n__p+&#x3D;&#39;&quot;;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return match;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    source +&#x3D; &quot;&#39;;\n&quot;;</span><br><span class="line"></span><br><span class="line">    source &#x3D; &#39;with(obj||&#123;&#125;)&#123;\n&#39; + source + &#39;&#125;\n&#39;</span><br><span class="line"></span><br><span class="line">    source &#x3D; &quot;var __t, __p&#x3D;&#39;&#39;;&quot; +</span><br><span class="line">        source + &#39;return __p;\n&#39;;</span><br><span class="line"></span><br><span class="line">    var render &#x3D; new Function(&#39;obj&#39;, source);</span><br><span class="line"></span><br><span class="line">    return render;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>其实原理也很简单，就是在执行多次匹配函数的时候，不断复制字符串，处理字符串，拼接字符串，最后拼接首尾代码，得到最终的代码字符串。</p>
<p>不过值得一提的是：在这段代码里，matcher 的表达式最后为：<code>/&lt;%=([\s\S]+?)%&gt;|&lt;%([\s\S]+?)%&gt;|$/g</code></p>
<p>问题是为什么还要加个 <code>|$</code> 呢？我们来看下 $：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">var str &#x3D; &quot;abc&quot;;</span><br><span class="line">str.replace(&#x2F;$&#x2F;g, function(match, offset)&#123;</span><br><span class="line">    console.log(typeof match) &#x2F;&#x2F; 空字符串</span><br><span class="line">    console.log(offset) &#x2F;&#x2F; 3</span><br><span class="line">    return match</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>我们之所以匹配 $，是为了获取最后一个字符串的位置，这样当我们 text.slice(index, offset)的时候，就可以截取到最后一个字符。</p>
<p>完整的使用代码可以参考 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template7">template 示例七</a>。</p>
<h3 id="最终版"><a href="#最终版" class="headerlink" title="最终版"></a>最终版</h3><p>其实代码写到这里，就已经跟 underscore 的实现很接近了，只是 underscore 加入了更多细节的处理，比如：</p>
<ol>
<li>对数据的转义功能</li>
<li>可传入配置项</li>
<li>对错误的处理</li>
<li>添加 source 属性，以方便查看代码字符串</li>
<li>添加了方便调试的 print 函数</li>
<li>…</li>
</ol>
<p>但是这些内容都还算简单，就不一版一版写了，最后的版本在 <a target="_blank" rel="noopener" href="https://github.com/mqyqingfeng/Blog/tree/master/demos/template/template8">template 示例八</a>，如果对其中有疑问，欢迎留言讨论</p>
<h3 id="面试简化版"><a href="#面试简化版" class="headerlink" title="面试简化版"></a>面试简化版</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">let template &#x3D; &#39;我是&#123;&#123;name&#125;&#125;，年龄&#123;&#123;age&#125;&#125;，性别&#123;&#123;sex&#125;&#125;&#39;;</span><br><span class="line">let data &#x3D; &#123;</span><br><span class="line">    name: &#39;姓名&#39;,</span><br><span class="line">    age: 18</span><br><span class="line">&#125;</span><br><span class="line">render(template, data); &#x2F;&#x2F; 我是姓名，年龄18，性别undefined</span><br></pre></td></tr></table></figure>

<p>代码</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">template, data</span>) </span>&#123;</span><br><span class="line">     <span class="comment">// 模板字符串正则</span></span><br><span class="line">    <span class="keyword">const</span> reg = <span class="regexp">/\&#123;\&#123;(\w+)\&#125;\&#125;/</span>;</span><br><span class="line">    <span class="comment">// 判断模板里是否有模板字符串</span></span><br><span class="line">    <span class="keyword">if</span> (reg.test(template)) &#123;</span><br><span class="line">     	<span class="comment">// 查找当前模板里第一个模板字符串的字段</span></span><br><span class="line">        <span class="keyword">const</span> name = reg.exec(template)[<span class="number">1</span>];</span><br><span class="line">        <span class="comment">// 将第一个模板字符串渲染</span></span><br><span class="line">        template = template.replace(reg, data[name]);</span><br><span class="line">        <span class="comment">// 递归的渲染并返回渲染后的结构</span></span><br><span class="line">        <span class="keyword">return</span> render(template, data);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果模板没有模板字符串直接返回</span></span><br><span class="line">    <span class="keyword">return</span> template;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="22-转化为驼峰命名"><a href="#22-转化为驼峰命名" class="headerlink" title="22.转化为驼峰命名"></a>22.转化为驼峰命名</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> s1 = <span class="string">&quot;get-element-by-id&quot;</span></span><br><span class="line"><span class="comment">// 转化为 getElementById</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 把-后面的字母替换为大写字母</span></span><br><span class="line"><span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span>(<span class="params">s</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> s.replace(<span class="regexp">/-\w/g</span>, <span class="function"><span class="keyword">function</span>(<span class="params">x</span>) </span>&#123;</span><br><span class="line">        <span class="comment">// 首字母大写</span></span><br><span class="line">        <span class="keyword">return</span> x.slice(<span class="number">1</span>).toUpperCase();</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="23-查找字符串中出现最多的字符和个数"><a href="#23-查找字符串中出现最多的字符和个数" class="headerlink" title="23.查找字符串中出现最多的字符和个数"></a>23.查找字符串中出现最多的字符和个数</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> str = <span class="string">&quot;abcabcabcbbccccc&quot;</span>;</span><br><span class="line"><span class="keyword">let</span> num = <span class="number">0</span>;</span><br><span class="line"><span class="keyword">let</span> char = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"><span class="comment">// 自己用哈希表</span></span><br><span class="line"><span class="comment">// 使其按照一定的次序排列</span></span><br><span class="line">str = str.split(<span class="string">&#x27;&#x27;</span>).sort().join(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line"><span class="comment">// &quot;aaabbbbbcccccccc&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义正则表达式</span></span><br><span class="line"><span class="keyword">let</span> re = <span class="regexp">/(\w)\1+/g</span>;</span><br><span class="line">str.replace(re, <span class="function">(<span class="params">$<span class="number">0</span>, $<span class="number">1</span></span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(num &lt; $<span class="number">0.</span>length)&#123;</span><br><span class="line">        num = $<span class="number">0.</span>length;</span><br><span class="line">        char = $<span class="number">1</span>;        </span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">`字符最多的是<span class="subst">$&#123;char&#125;</span>，出现了<span class="subst">$&#123;num&#125;</span>次`</span>);</span><br></pre></td></tr></table></figure>

<h2 id="24-字符串查找"><a href="#24-字符串查找" class="headerlink" title="24.字符串查找"></a>24.字符串查找</h2><blockquote>
<p>请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中，并返回第一次出现的位置（找不到返回 -1）。</p>
</blockquote>
<h3 id="暴力解"><a href="#暴力解" class="headerlink" title="暴力解"></a>暴力解</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var strStr &#x3D; function(haystack, needle) &#123;</span><br><span class="line">    let m &#x3D;  haystack.length;</span><br><span class="line">    let n &#x3D; needle.length;</span><br><span class="line">    for (let i &#x3D; 0; i &lt;&#x3D; m - n; i++) &#123;</span><br><span class="line">        let j;</span><br><span class="line">        for (j &#x3D; 0; j &lt; n; j++) &#123;</span><br><span class="line">            if (needle[j] !&#x3D;&#x3D; haystack[i + j]) break;</span><br><span class="line">        &#125;</span><br><span class="line">        &#x2F;&#x2F; needle子串全都匹配了</span><br><span class="line">        if (j &#x3D;&#x3D;&#x3D; n) return i;</span><br><span class="line">    &#125;</span><br><span class="line">    &#x2F;&#x2F; haystack中不存在needle</span><br><span class="line">    return -1;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="KMP"><a href="#KMP" class="headerlink" title="KMP"></a>KMP</h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> strStr = <span class="function"><span class="keyword">function</span>(<span class="params">haystack, needle</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> k = -<span class="number">1</span>, n = haystack.length, p = needle.length;</span><br><span class="line">    <span class="keyword">if</span> (p == <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="comment">// -1表示不存在相同的最大前缀和后缀</span></span><br><span class="line">    <span class="keyword">let</span> next = <span class="built_in">Array</span>(p).fill(-<span class="number">1</span>);</span><br><span class="line">    <span class="comment">// 计算next数组</span></span><br><span class="line">    calNext(needle, next);</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (k &gt; -<span class="number">1</span> &amp;&amp; needle[k + <span class="number">1</span>] != haystack[i]) &#123;</span><br><span class="line">            <span class="comment">// 有部分匹配，往前回溯</span></span><br><span class="line">            k = next[k];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (needle[k + <span class="number">1</span>] == haystack[i]) &#123;</span><br><span class="line">            k++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (k == p - <span class="number">1</span>) &#123;</span><br><span class="line">            <span class="comment">// 说明k移动到needle的最末端，返回相应的位置</span></span><br><span class="line">            <span class="keyword">return</span> i - p + <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> -<span class="number">1</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 辅函数- 计算next数组</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">calNext</span>(<span class="params">needle, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> j = <span class="number">1</span>, p = -<span class="number">1</span>; j &lt; needle.length; j++) &#123;</span><br><span class="line">        <span class="keyword">while</span> (p &gt; -<span class="number">1</span> &amp;&amp; needle[p + <span class="number">1</span>] != needle[j]) &#123;</span><br><span class="line">            <span class="comment">// 如果下一位不同，往前回溯</span></span><br><span class="line">            p = next[p];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (needle[p + <span class="number">1</span>] == needle[j]) &#123;</span><br><span class="line">            <span class="comment">// 如果下一位相同，更新相同的最大前缀和最大后缀长</span></span><br><span class="line">            p++;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 位置j处更新最长前缀</span></span><br><span class="line">        next[j] = p;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>马拉车水平不够</p>
<h2 id="25-实现千位分隔符"><a href="#25-实现千位分隔符" class="headerlink" title="25.实现千位分隔符"></a>25.实现千位分隔符</h2><h3 id="1-方法一"><a href="#1-方法一" class="headerlink" title="1.方法一"></a>1.方法一</h3><p>实现思路是将数字转换为字符数组，再循环整个数组， 每三位添加一个分隔逗号，最后再合并成字符串。因为分隔符在顺序上是从后往前添加的：比如 1234567添加后是1,234,567 而不是 123,456,7 ，所以方便起见可以先把数组倒序，添加完之后再倒序回来，就是正常的顺序了。要注意的是如果数字带小数的话，要把小数部分分开处理。</p>
<figure class="highlight swift"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">function numFormat(num) &#123;</span><br><span class="line">  <span class="comment">// 按小数点分割</span></span><br><span class="line">  num <span class="operator">=</span> num.toString().split(<span class="string">&quot;.&quot;</span>);</span><br><span class="line">  <span class="comment">// 转换成字符数组并且倒序排列</span></span><br><span class="line">  <span class="keyword">let</span> arr <span class="operator">=</span> num[<span class="number">0</span>].split(<span class="string">&quot;&quot;</span>).reverse();</span><br><span class="line">  <span class="keyword">let</span> res <span class="operator">=</span> [];</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i <span class="operator">=</span> <span class="number">0</span>; i <span class="operator">&lt;</span> arr.length; i<span class="operator">++</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (i <span class="operator">%</span> <span class="number">3</span> <span class="operator">===</span> <span class="number">0</span> <span class="operator">&amp;&amp;</span> i <span class="operator">!==</span> <span class="number">0</span>) &#123;</span><br><span class="line">      <span class="comment">// 添加分隔符</span></span><br><span class="line">      res.push(<span class="string">&quot;,&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    res.push(arr[i]);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// 再次倒序成为正确的顺序</span></span><br><span class="line">  res.reverse();</span><br><span class="line">  <span class="comment">// 如果有小数的话添加小数部分</span></span><br><span class="line">  <span class="keyword">if</span> (num[<span class="number">1</span>]) &#123;</span><br><span class="line">    res <span class="operator">=</span> res.join(<span class="string">&quot;&quot;</span>).concat(<span class="string">&quot;.&quot;</span> <span class="operator">+</span> num[<span class="number">1</span>]);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    res <span class="operator">=</span> res.join(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a <span class="operator">=</span> <span class="number">1234567894532</span>;</span><br><span class="line"><span class="keyword">var</span> b <span class="operator">=</span> <span class="number">673439.4542</span>;</span><br><span class="line">console.log(numFormat(a)); <span class="comment">// &quot;1,234,567,894,532&quot;</span></span><br><span class="line">console.log(numFormat(b)); <span class="comment">// &quot;673,439.4542&quot;</span></span><br></pre></td></tr></table></figure>

<h3 id="2-方法二"><a href="#2-方法二" class="headerlink" title="2.方法二"></a>2.方法二</h3><p>使用JS自带的函数 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString">toLocaleString</a></p>
<blockquote>
<p>语法：  <code>numObj.toLocaleString([locales [, options]])</code></p>
</blockquote>
<p><code>toLocaleString()</code> 方法返回这个数字在特定语言环境下的表示字符串。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> a = <span class="number">1234567894532</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">673439.4542</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">console</span>.log(a.toLocaleString());  <span class="comment">// &quot;1,234,567,894,532&quot;</span></span><br><span class="line"><span class="built_in">console</span>.log(b.toLocaleString());  <span class="comment">// &quot;673,439.454&quot;  （小数部分四舍五入了）</span></span><br></pre></td></tr></table></figure>

<p>要注意的是这个函数在没有指定区域的基本使用时，返回使用默认的语言环境和默认选项格式化的字符串，所以不同地区数字格式可能会有一定的差异。最好确保使用 locales 参数指定了使用的语言。<br> 注：我测试的环境下小数部分会根据四舍五入只留下三位。</p>
<h3 id="3-方法三"><a href="#3-方法三" class="headerlink" title="3. 方法三"></a>3. 方法三</h3><p>使用<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions">正则表达式</a>和<a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace">replace</a>函数，相对前两种我更喜欢这种方法，虽然正则有点难以理解。</p>
<blockquote>
<p>replace 语法：<code>str.replace(regexp|substr, newSubStr|function)</code></p>
</blockquote>
<p>其中第一个 <a target="_blank" rel="noopener" href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/RegExp">RegExp
 </a> 对象或者其字面量所匹配的内容会被第二个参数的返回值替换。</p>
<figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">numFormat</span>(<span class="params">num</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> res = num.toString().replace(<span class="regexp">/\d+/</span>, <span class="function"><span class="keyword">function</span> (<span class="params">n</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// 先提取整数部分</span></span><br><span class="line">    <span class="keyword">return</span> n.replace(<span class="regexp">/(\d)(?=(\d&#123;3&#125;)+$)/g</span>, <span class="function"><span class="keyword">function</span> (<span class="params">$<span class="number">1</span></span>) </span>&#123;</span><br><span class="line">      <span class="keyword">return</span> $<span class="number">1</span> + <span class="string">&quot;,&quot;</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="keyword">return</span> res;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1234567894532</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">673439.4542</span>;</span><br><span class="line"><span class="built_in">console</span>.log(numFormat(a)); <span class="comment">// &quot;1,234,567,894,532&quot;</span></span><br><span class="line"><span class="built_in">console</span>.log(numFormat(b)); <span class="comment">// &quot;673,439.4542&quot;</span></span><br></pre></td></tr></table></figure>

<p>正则表达式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">var thousandSeparator &#x3D; function(n) &#123;</span><br><span class="line">    return (n + &#39;&#39;).replace(&#x2F;(?!^)(?&#x3D;(\d&#123;3&#125;)+$)&#x2F;g, &#39;.&#39;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>根据定义</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var thousandSeparator &#x3D; function(n) &#123;</span><br><span class="line">    let count &#x3D; 0;</span><br><span class="line">    let ans &#x3D; &quot;&quot;;</span><br><span class="line">    do &#123;</span><br><span class="line">        let cur &#x3D; n % 10;</span><br><span class="line">        n &#x3D; Math.floor(n &#x2F; 10);</span><br><span class="line">        ans +&#x3D; cur;</span><br><span class="line">        count++;</span><br><span class="line">        if (count % 3 &#x3D;&#x3D; 0 &amp;&amp; n) &#123;</span><br><span class="line">            ans +&#x3D; &quot;.&quot;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; while (n);</span><br><span class="line">    return ans.split(&#39;&#39;).reverse().join(&#39;&#39;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="26-判断是否是电话号码"><a href="#26-判断是否是电话号码" class="headerlink" title="26.判断是否是电话号码"></a>26.判断是否是电话号码</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function isPhone(tel) &#123;</span><br><span class="line">    var regx &#x3D; &#x2F;^1[34578]\d&#123;9&#125;$&#x2F;;</span><br><span class="line">    return regx.test(tel);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="27-验证是否是邮箱"><a href="#27-验证是否是邮箱" class="headerlink" title="27.验证是否是邮箱"></a>27.验证是否是邮箱</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">function isEmail(email) &#123;</span><br><span class="line">    var regx &#x3D; &#x2F;^([a-zA-Z0-9_\-]+@([a-zA-Z0-9_\-]+)+$&#x2F;;</span><br><span class="line">    return regx.test(email);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="28-用ES5实现数组的map方法"><a href="#28-用ES5实现数组的map方法" class="headerlink" title="28.用ES5实现数组的map方法"></a>28.用ES5实现数组的map方法</h2><ul>
<li>回调函数的参数有哪些，返回值如何处理</li>
<li>不修改原来的数组</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Array</span>.prototype.MyMap = <span class="function"><span class="keyword">function</span>(<span class="params">fn, context</span>)</span>&#123;</span><br><span class="line">    <span class="comment">// 转换类数组，由于是ES5所以就不用...展开符了</span></span><br><span class="line">    <span class="keyword">let</span> arr = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">this</span>);</span><br><span class="line">    <span class="keyword">let</span> mappedArr = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; arr.length; i++) &#123;</span><br><span class="line">        <span class="comment">// 把当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))</span></span><br><span class="line">        mappedArr.push(fn.call(context, arr[i], i, <span class="built_in">this</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> mappedArr;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="29-用ES5实现数组的reduce方法"><a href="#29-用ES5实现数组的reduce方法" class="headerlink" title="29.用ES5实现数组的reduce方法"></a>29.用ES5实现数组的reduce方法</h2><ul>
<li>初始值不传怎么处理</li>
<li>回调函数的参数有哪些，返回值如何处理。</li>
</ul>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Array.prototype.myReduce &#x3D; function(fn, initialValue) &#123;</span><br><span class="line">    let arr &#x3D; Array.prototpye.slice.call(this);</span><br><span class="line">    let res, startIndex;</span><br><span class="line">    &#x2F;&#x2F; 不传默认取数组第一项</span><br><span class="line">    res &#x3D; initialValue ? initialValue : arr[0];</span><br><span class="line">    startIndex &#x3D; initialValue ? 0 : 1;</span><br><span class="line">    for (let i &#x3D; startIndex; i &lt; arr.length; i++) &#123;</span><br><span class="line">    	&#x2F;&#x2F; 把初始值、当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].reduce((initVal,curr,index,arr))</span><br><span class="line">    	res &#x3D; fn.call(null, res, arr[i], i, this);</span><br><span class="line">    &#125;</span><br><span class="line">    return res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>对于普通函数，绑定this指向</li>
<li>对于构造函数，要保证原函数的原型对象上的属性不能丢失</li>
</ul>
<h2 id="30-请实现一个-add-函数，满足以下功能"><a href="#30-请实现一个-add-函数，满足以下功能" class="headerlink" title="30.请实现一个 add 函数，满足以下功能"></a>30.请实现一个 add 函数，满足以下功能</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">add(<span class="number">1</span>); 			<span class="comment">// 1</span></span><br><span class="line">add(<span class="number">1</span>)(<span class="number">2</span>);  	<span class="comment">// 3</span></span><br><span class="line">add(<span class="number">1</span>)(<span class="number">2</span>)(<span class="number">3</span>)；<span class="comment">// 6</span></span><br><span class="line">add(<span class="number">1</span>)(<span class="number">2</span>, <span class="number">3</span>); <span class="comment">// 6</span></span><br><span class="line">add(<span class="number">1</span>, <span class="number">2</span>)(<span class="number">3</span>); <span class="comment">// 6</span></span><br><span class="line">add(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>); <span class="comment">// 6</span></span><br></pre></td></tr></table></figure>

<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">add</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">	<span class="keyword">let</span> args = [].slice.call(<span class="built_in">arguments</span>);</span><br><span class="line">	</span><br><span class="line">	<span class="keyword">let</span> fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">		<span class="keyword">let</span> fn_args = [].slice.call(<span class="built_in">arguments</span>)</span><br><span class="line">		<span class="keyword">return</span> add.apply(<span class="literal">null</span>, args.concat(fn_args))</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	fn.toString = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">		returm args.reduce(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a + b)</span><br><span class="line">	&#125;</span><br><span class="line">	</span><br><span class="line">	<span class="keyword">return</span> fn</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="31-实现一个-sleep-函数，比如-sleep-1000-意味着等待1000毫秒"><a href="#31-实现一个-sleep-函数，比如-sleep-1000-意味着等待1000毫秒" class="headerlink" title="31.实现一个 sleep 函数，比如 sleep(1000) 意味着等待1000毫秒"></a>31.实现一个 sleep 函数，比如 sleep(1000) 意味着等待1000毫秒</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">const sleep &#x3D; (time) &#x3D;&gt; &#123;</span><br><span class="line">	return new Promise(resolve &#x3D;&gt; setTimeout(resolve, time))</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">sleep(1000).then(() &#x3D;&gt; &#123;</span><br><span class="line">	&#x2F;&#x2F; 这里写你的函数</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="32-实现-5-add-3-minus-2-功能"><a href="#32-实现-5-add-3-minus-2-功能" class="headerlink" title="32.实现 (5).add(3).minus(2) 功能"></a>32.实现 (5).add(3).minus(2) 功能</h2><blockquote>
<p>例： 5 + 3 - 2，结果为 6</p>
</blockquote>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Number</span>.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params">n</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.valueOf() + n;</span><br><span class="line">&#125;</span><br><span class="line"><span class="built_in">Number</span>.prototype.minus = <span class="function"><span class="keyword">function</span>(<span class="params">n</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>.valueOf() - n;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="33-实现一个双向绑定"><a href="#33-实现一个双向绑定" class="headerlink" title="33.实现一个双向绑定"></a>33.实现一个双向绑定</h2><h3 id="defineProperty-版本"><a href="#defineProperty-版本" class="headerlink" title="defineProperty 版本"></a>defineProperty 版本</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 数据</span><br><span class="line">const data &#x3D; &#123;</span><br><span class="line">	text: &#39;default&#39;</span><br><span class="line">&#125;;</span><br><span class="line">const input  &#x3D; document.getElementById(&#39;input&#39;);</span><br><span class="line">const span  &#x3D; document.getElementById(&#39;span&#39;);</span><br><span class="line">&#x2F;&#x2F; 数据劫持</span><br><span class="line">Object.defineProperty(data, &#39;text&#39;, &#123;</span><br><span class="line">	&#x2F;&#x2F; 数据变化 --&gt; 修改视图</span><br><span class="line">	set(newVal) &#123;</span><br><span class="line">		input.value &#x3D; newVal;</span><br><span class="line">		span.innerHTML &#x3D; newVal;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;);</span><br><span class="line">&#x2F;&#x2F; 数据变化 --&gt; 修改视图</span><br><span class="line">input.addEventLisener(&#39;keyup&#39;, function(e) &#123;</span><br><span class="line">	data.text &#x3D; e.target.value;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="proxy-版本"><a href="#proxy-版本" class="headerlink" title="proxy 版本"></a>proxy 版本</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&#x2F;&#x2F; 数据</span><br><span class="line">const data &#x3D; &#123;</span><br><span class="line">	text: &#39;default&#39;</span><br><span class="line">&#125;;</span><br><span class="line">const input  &#x3D; document.getElementById(&#39;input&#39;);</span><br><span class="line">const span  &#x3D; document.getElementById(&#39;span&#39;);</span><br><span class="line">&#x2F;&#x2F; 数据劫持</span><br><span class="line">const handler &#x3D; &#123;</span><br><span class="line">	set(target, key, value) &#123;</span><br><span class="line">		target[key] &#x3D; value;</span><br><span class="line">		&#x2F;&#x2F; 数据变化 --&gt; 修改视图</span><br><span class="line">		input.value &#x3D; newVal;</span><br><span class="line">		span.innerHTML &#x3D; newVal;</span><br><span class="line">		return value;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;;</span><br><span class="line">const proxy &#x3D; new Proxy(data, handler);</span><br><span class="line"></span><br><span class="line">&#x2F;&#x2F; 视图更改 --&gt; 数据变化</span><br><span class="line">input.addEventLisener(&#39;keyup&#39;, function(e) &#123;</span><br><span class="line">	proxy.text &#x3D; e.target.value;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="34-Array-isArray-实现"><a href="#34-Array-isArray-实现" class="headerlink" title="34.Array.isArray 实现"></a>34.Array.isArray 实现</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Array.myIsArray &#x3D; function(o) &#123;</span><br><span class="line">	return Object.prototype.toString.call(Object(o)) &#x3D;&#x3D;&#x3D; &#39;[object Array]&#39;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">console.log(Array.myIsArray([])); &#x2F;&#x2F; true</span><br></pre></td></tr></table></figure>

<h2 id="35-实现一个函数判断数据类型"><a href="#35-实现一个函数判断数据类型" class="headerlink" title="35.实现一个函数判断数据类型"></a>35.实现一个函数判断数据类型</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getType</span>(<span class="params">obj</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (obj === <span class="literal">null</span>) <span class="keyword">return</span> <span class="built_in">String</span>(obj);</span><br><span class="line">    <span class="comment">// 对象类型 &quot;[object XXX]&quot;</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">typeof</span> obj === <span class="string">&#x27;object&#x27;</span> ? <span class="built_in">Object</span>.prototype.toString.call(obj).replace(<span class="string">&#x27;[object &#x27;</span>, <span class="string">&#x27;&#x27;</span>).replace(<span class="string">&#x27;]&#x27;</span>, <span class="string">&#x27;&#x27;</span>).toLowerCase() : <span class="keyword">typeof</span> obj;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用</span></span><br><span class="line">getType(<span class="literal">null</span>); <span class="comment">// -&gt; null</span></span><br><span class="line">getType(<span class="literal">undefined</span>); <span class="comment">// -&gt; undefined</span></span><br><span class="line">getType(&#123;&#125;); <span class="comment">// -&gt; object</span></span><br><span class="line">getType([]); <span class="comment">// -&gt; array</span></span><br><span class="line">getType(<span class="number">123</span>); <span class="comment">// -&gt; number</span></span><br><span class="line">getType(<span class="literal">true</span>); <span class="comment">// -&gt; boolean</span></span><br><span class="line">getType(<span class="string">&#x27;123&#x27;</span>); <span class="comment">// -&gt; string</span></span><br><span class="line">getType(<span class="regexp">/123/</span>); <span class="comment">// -&gt; regexp</span></span><br><span class="line">getType(<span class="keyword">new</span> <span class="built_in">Date</span>()); <span class="comment">// -&gt; date</span></span><br></pre></td></tr></table></figure>

<h2 id="36-实现Event-event-bus"><a href="#36-实现Event-event-bus" class="headerlink" title="36. 实现Event(event bus)"></a>36. 实现Event(event bus)</h2><blockquote>
<p>event bus既是node中各个模块的基石，又是前端组件通信的依赖手段之一，同时涉及了订阅-发布设计模式，是非常重要的基础</p>
</blockquote>
<p><strong>简单版：</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventEmeitter</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>._events = <span class="built_in">this</span>._events || <span class="keyword">new</span> <span class="built_in">Map</span>(); <span class="comment">// 储存事件/回调键值对</span></span><br><span class="line">    <span class="built_in">this</span>._maxListeners = <span class="built_in">this</span>._maxListeners || <span class="number">10</span>; <span class="comment">// 设立监听上限</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 触发名为type的事件</span></span><br><span class="line">EventEmeitter.prototype.emit = <span class="function"><span class="keyword">function</span>(<span class="params">type, ...args</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 从储存事件键值对的this._events中获取对应事件回调函数</span></span><br><span class="line">  <span class="keyword">let</span> handler = <span class="built_in">this</span>._events.get(type);</span><br><span class="line">  <span class="keyword">if</span> (args.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    handler.apply(<span class="built_in">this</span>, args);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    handler.call(<span class="built_in">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听名为type的事件</span></span><br><span class="line">EventEmeitter.prototype.addListener = <span class="function"><span class="keyword">function</span>(<span class="params">type, fn</span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 将type事件以及对应的fn函数放入this._events中储存</span></span><br><span class="line">  <span class="keyword">if</span> (!<span class="built_in">this</span>._events.get(type)) &#123;</span><br><span class="line">    <span class="built_in">this</span>._events.set(type, fn);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p><strong>面试版：</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">EventEmeitter</span> </span>&#123;</span><br><span class="line">  <span class="function"><span class="title">constructor</span>(<span class="params"></span>)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>._events = <span class="built_in">this</span>._events || <span class="keyword">new</span> <span class="built_in">Map</span>(); <span class="comment">// 储存事件/回调键值对</span></span><br><span class="line">    <span class="built_in">this</span>._maxListeners = <span class="built_in">this</span>._maxListeners || <span class="number">10</span>; <span class="comment">// 设立监听上限</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 触发名为type的事件</span></span><br><span class="line">EventEmeitter.prototype.emit = <span class="function"><span class="keyword">function</span>(<span class="params">type, ...args</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">let</span> handler;</span><br><span class="line">  handler = <span class="built_in">this</span>._events.get(type);</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Array</span>.isArray(handler)) &#123;</span><br><span class="line">    <span class="comment">// 如果是一个数组说明有多个监听者,需要依次此触发里面的函数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; handler.length; i++) &#123;</span><br><span class="line">      <span class="keyword">if</span> (args.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        handler[i].apply(<span class="built_in">this</span>, args);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        handler[i].call(<span class="built_in">this</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 单个函数的情况我们直接触发即可</span></span><br><span class="line">    <span class="keyword">if</span> (args.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">      handler.apply(<span class="built_in">this</span>, args);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      handler.call(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 监听名为type的事件</span></span><br><span class="line">EventEmeitter.prototype.addListener = <span class="function"><span class="keyword">function</span>(<span class="params">type, fn</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> handler = <span class="built_in">this</span>._events.get(type); <span class="comment">// 获取对应事件名称的函数清单</span></span><br><span class="line">  <span class="keyword">if</span> (!handler) &#123;</span><br><span class="line">    <span class="built_in">this</span>._events.set(type, fn);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (handler &amp;&amp; <span class="keyword">typeof</span> handler === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="comment">// 如果handler是函数说明只有一个监听者</span></span><br><span class="line">    <span class="built_in">this</span>._events.set(type, [handler, fn]); <span class="comment">// 多个监听者我们需要用数组储存</span></span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    handler.push(fn); <span class="comment">// 已经有多个监听者,那么直接往数组里push函数即可</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">EventEmeitter.prototype.removeListener = <span class="function"><span class="keyword">function</span>(<span class="params">type, fn</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> handler = <span class="built_in">this</span>._events.get(type); <span class="comment">// 获取对应事件名称的函数清单</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果是函数,说明只被监听了一次</span></span><br><span class="line">  <span class="keyword">if</span> (handler &amp;&amp; <span class="keyword">typeof</span> handler === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    <span class="built_in">this</span>._events.delete(type, fn);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">let</span> postion;</span><br><span class="line">    <span class="comment">// 如果handler是数组,说明被监听多次要找到对应的函数</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; handler.length; i++) &#123;</span><br><span class="line">      <span class="keyword">if</span> (handler[i] === fn) &#123;</span><br><span class="line">        postion = i;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        postion = -<span class="number">1</span>;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 如果找到匹配的函数,从数组中清除</span></span><br><span class="line">    <span class="keyword">if</span> (postion !== -<span class="number">1</span>) &#123;</span><br><span class="line">      <span class="comment">// 找到数组对应的位置,直接清除此回调</span></span><br><span class="line">      handler.splice(postion, <span class="number">1</span>);</span><br><span class="line">      <span class="comment">// 如果清除后只有一个函数,那么取消数组,以函数形式保存</span></span><br><span class="line">      <span class="keyword">if</span> (handler.length === <span class="number">1</span>) &#123;</span><br><span class="line">        <span class="built_in">this</span>._events.set(type, handler[<span class="number">0</span>]);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="37-模拟new"><a href="#37-模拟new" class="headerlink" title="37.模拟new"></a>37.模拟new</h2><p><strong>new操作符做了这些事：</strong></p>
<ul>
<li>创建一个全新的对象，这个对象的<code>__proto__</code>要指向构造函数的原型对象</li>
<li>执行构造函数</li>
<li>返回值为object类型则作为new方法的返回值返回，否则返回上述全新对象</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">myNew</span>(<span class="params">fn, ...args</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> instance = <span class="built_in">Object</span>.create(fn.prototype);</span><br><span class="line">    <span class="keyword">let</span> res = fn.apply(instance, args);</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">typeof</span> res === <span class="string">&#x27;object&#x27;</span> ? res: instance;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="38-手写实现set和map"><a href="#38-手写实现set和map" class="headerlink" title="38.手写实现set和map"></a>38.手写实现set和map</h2><h3 id="1-Set"><a href="#1-Set" class="headerlink" title="1.Set"></a>1.Set</h3><p>ES6提供给我们的构造函数，能够造出一种新的存储数据的结构，只有属性值，成员值唯一（不重复）。</p>
<pre><code>class MySet&#123;
    constructor(iterator)&#123;
        // 判断是否是可迭代对象
        if(typeof iterator[Symbol.iterator] !== &quot;function&quot;)&#123;
            throw new Error(`你提供的$&#123;iterator&#125;不是一个课迭代的对象`);
        &#125;
        this._datas = [];
        // 循环可迭代对象，将结果加入到set中
        for (const item of iterator) &#123;
            this.add(item);
        &#125;
    &#125;
    add(data)&#123;
        if(!this.has(data))&#123;
            this._datas.push(data);
        &#125;
    &#125;
    has(data)&#123;
        for (const item of this._datas) &#123;
            if(this.isEqual(data,item))&#123;
                return true;
            &#125;
        &#125;
        return false;
    &#125;
    delete(data)&#123;
        for (let i = 0; i &lt; this._datas.length; i++) &#123;
            const element = this._datas[i];
            if(this.isEqual(element, data))&#123;
                this._datas.splice(i,1);
                return true;
            &#125;

        &#125;
        return false;
    &#125;
    // 遍历
    *[Symbol.iterator]()&#123;
        for (const item of this._datas) &#123;
            yield item;
        &#125;
    &#125;
    forEach(callback)&#123;
        for (const item of this._datas) &#123;
            callback(item,ietm,this);
        &#125;
    &#125;
    clear()&#123;
        this._datas.length = 0;
    &#125;

    /**
     * 判断两个数据是否相等
     * @param &#123;*&#125; data1 
     * @param &#123;*&#125; data2 
     */
    isEqual(data1,data2)&#123;
        if(data1 === 0 &amp;&amp; data2 === 0)&#123;
            return true;
        &#125;
        return Object.is(data1,data2);
    &#125;
&#125;
</code></pre>
<h3 id="2-Map"><a href="#2-Map" class="headerlink" title="2.Map"></a>2.Map</h3><p>ES6提供给我们的构造函数，能够造出一种新的存储数据的结构。本质上是键值对的集合。key对应value，key和value唯一，任何值都可以当属性。</p>
<pre><code>class MyMap&#123;
    constructor(iterable = [])&#123;
         //判断是否是可迭代对象
         if(typeof iterable[Symbol.iterator] !== &quot;function&quot;)&#123;
            throw new Error(`你提供的$&#123;iterable&#125;不是一个课迭代的对象`);
        &#125;
        this._datas = [];
        for (const item of iterable) &#123;
            //item也是一个可迭代的对象
            if(typeof item[Symbol.iterator] !== &quot;function&quot;)&#123;
                throw new Error(`你提供的$&#123;item&#125;不是一个课迭代的对象`)
            &#125;
            const iterator = item[Symbol.iterator]();
            const key = iterator.next().value;
            const value = iterator.next().value;
            this.set(key,value);
        &#125;
    &#125;
    set(key, value)&#123;
        //看里面有没有，如果有，则直接修改key对应的value值
        const obj = this._getObj(key);
        if(obj)&#123;
            //修改
            obj.value = value;
        &#125;else&#123;
            this._datas.push(&#123;
                key,
                value
            &#125;)
        &#125;

    &#125;
    get(key)&#123;
        const item = this._getObj(key);
        if(item)&#123;
            return item.value;
        &#125;
        return undefined;
    &#125;
    get_size()&#123;
        return this._datas.length;
    &#125;
    delete(key)&#123;
       for (let i = 0; i &lt; this._datas.length; i++) &#123;
           const element = this._datas[i];
           if(this.isEqual(element.key,key))&#123;
               this._datas.splice(i,1);
               return true;
           &#125;
       &#125;
       return false;
    &#125;
    clear()&#123;
        this._datas.length = 0;
    &#125;
    has(key)&#123;
        const item = this._getObj(key);
        return item !== undefined;
    &#125;
    /**
     * 根据key值找到对应的数组项
     * @param &#123;*&#125; key 
     */
    _getObj(key)&#123;
        for (const item of this._datas) &#123;
            if(this.isEqual(item.key,key))&#123;
                return item;
            &#125;
        &#125;
        return undefined;
    &#125;
    isEqual(data1,data2)&#123;
        if(data1 === 0 &amp;&amp; data2 === 0)&#123;
            return true;
        &#125;
        return Object.is(data1,data2);
    &#125;
    *[Symbol.iterator]()&#123;
        for (const item of this._datas) &#123;
            yield[item.key,item.value];
        &#125;
    &#125;
    forEach(callback)&#123;
        for (const item of this._datas) &#123;
            callback(item.value,item.key,this);
        &#125;
    &#125;
&#125;
</code></pre>

    </div>

    
    
    
      
<div>
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
</div>
        

  <div class="followme">
    <p>欢迎关注我的其它发布渠道</p>

    <div class="social-list">

        <div class="social-item">
          <a target="_blank" class="social-link" href="/atom.xml">
            <span class="icon">
              <i class="fa fa-rss"></i>
            </span>

            <span class="label">RSS</span>
          </a>
        </div>
    </div>
  </div>


      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/javascript/" rel="tag"><i class="fa fa-tag"></i> javascript</a>
              <a href="/tags/ES6/" rel="tag"><i class="fa fa-tag"></i> ES6</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item"></div>
      <div class="post-nav-item">
    <a href="/2020/10/20/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" rel="next" title="设计模式">
      设计模式 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#1-%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0call-apply-bind"><span class="nav-text">1.手动实现call,apply,bind</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0call"><span class="nav-text">模拟实现call</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0apply"><span class="nav-text">模拟实现apply</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0bind"><span class="nav-text">模拟实现bind</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E5%A4%9F%E7%94%A8%E7%89%88"><span class="nav-text">面试够用版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%89%A9%E5%B1%95"><span class="nav-text">扩展</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#2-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F"><span class="nav-text">2.观察者模式</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BC%98%E7%82%B9"><span class="nav-text">优点</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Nodejs%E7%9A%84EventEmitter"><span class="nav-text">Nodejs的EventEmitter</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Api"><span class="nav-text">Api</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8"><span class="nav-text">基本使用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0EventEmitter"><span class="nav-text">手动实现EventEmitter</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#JavaScript%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6"><span class="nav-text">JavaScript自定义事件</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#3-%E9%98%B2%E6%8A%96-debounce"><span class="nav-text">3.防抖(debounce)</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%9F%E7%90%86"><span class="nav-text">原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%98%B2%E6%8A%96%E6%98%AF%E4%BB%80%E4%B9%88"><span class="nav-text">防抖是什么</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%B8%80%E7%89%88"><span class="nav-text">第一版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#this"><span class="nav-text">this</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#event-%E5%AF%B9%E8%B1%A1"><span class="nav-text">event 对象</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AB%8B%E5%88%BB%E6%89%A7%E8%A1%8C"><span class="nav-text">立刻执行</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%BF%94%E5%9B%9E%E5%80%BC"><span class="nav-text">返回值</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%96%E6%B6%88"><span class="nav-text">取消</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E7%89%88%E4%BB%A3%E7%A0%81"><span class="nav-text">面试版代码</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#4-%E8%8A%82%E6%B5%81-throttle"><span class="nav-text">4.节流(throttle)</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%9F%E7%90%86-1"><span class="nav-text">原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%8A%82%E6%B5%81%E6%98%AF%E4%BB%80%E4%B9%88"><span class="nav-text">节流是什么</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E6%97%B6%E9%97%B4%E6%88%B3"><span class="nav-text">使用时间戳</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E5%AE%9A%E6%97%B6%E5%99%A8"><span class="nav-text">使用定时器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%8C%E5%89%91%E5%90%88%E7%92%A7"><span class="nav-text">双剑合璧</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BC%98%E5%8C%96"><span class="nav-text">优化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%96%E6%B6%88-1"><span class="nav-text">取消</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B3%A8%E6%84%8F"><span class="nav-text">注意</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E7%89%88%E4%BB%A3%E7%A0%81-1"><span class="nav-text">面试版代码</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5-%E6%B5%85%E6%8B%B7%E8%B4%9D%E5%92%8C%E6%B7%B1%E6%8B%B7%E8%B4%9D"><span class="nav-text">5.浅拷贝和深拷贝</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B5%85%E6%8B%B7%E8%B4%9D"><span class="nav-text">浅拷贝</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%B7%B1%E6%8B%B7%E8%B4%9D"><span class="nav-text">深拷贝</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E7%89%88%EF%BC%9A"><span class="nav-text">简单版：</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%B1%80%E9%99%90%E6%80%A7%EF%BC%9A"><span class="nav-text">局限性：</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E7%89%88-%E9%80%92%E5%BD%92"><span class="nav-text">面试版:递归</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8"><span class="nav-text">循环引用</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96"><span class="nav-text">性能优化</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%85%B6%E4%BB%96%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="nav-text">其他数据类型</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%90%88%E7%90%86%E7%9A%84%E5%88%A4%E6%96%AD%E5%BC%95%E7%94%A8%E7%B1%BB%E5%9E%8B"><span class="nav-text">合理的判断引用类型</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="nav-text">获取数据类型</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E5%8F%AF%E7%BB%A7%E7%BB%AD%E9%81%8D%E5%8E%86%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="nav-text">可继续遍历的类型</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E4%B8%8D%E5%8F%AF%E7%BB%A7%E7%BB%AD%E9%81%8D%E5%8E%86%E7%9A%84%E7%B1%BB%E5%9E%8B"><span class="nav-text">不可继续遍历的类型</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%85%8B%E9%9A%86%E5%87%BD%E6%95%B0"><span class="nav-text">克隆函数</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%9C%80%E5%90%8E"><span class="nav-text">最后</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%8F%82%E8%80%83"><span class="nav-text">参考</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#6-%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D%E3%80%81%E6%89%81%E5%B9%B3%E3%80%81%E6%9C%80%E5%80%BC"><span class="nav-text">6.数组去重、扁平、最值</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%BB%E9%87%8D"><span class="nav-text">去重</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Object"><span class="nav-text">Object</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#indexOf-filter"><span class="nav-text">indexOf + filter</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Set"><span class="nav-text">Set</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%89%81%E5%B9%B3"><span class="nav-text">扁平</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%B8%80%E6%AE%B5%E4%BB%A3%E7%A0%81%E6%80%BB%E7%BB%93-Array-prototype-flat-%E7%89%B9%E6%80%A7"><span class="nav-text">一段代码总结 Array.prototype.flat() 特性</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Array-prototype-flat-%E7%89%B9%E6%80%A7%E6%80%BB%E7%BB%93"><span class="nav-text">Array.prototype.flat() 特性总结</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E5%AE%98-N-%E8%BF%9E%E9%97%AE"><span class="nav-text">面试官 N 连问</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E4%B8%80%E9%97%AE%EF%BC%9A%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E6%95%B0%E7%BB%84%E6%8B%8D%E5%B9%B3-flat-%E5%87%BD%E6%95%B0"><span class="nav-text">第一问：实现一个简单的数组拍平 flat 函数</span></a><ol class="nav-child"><li class="nav-item nav-level-6"><a class="nav-link" href="#%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF"><span class="nav-text">实现思路</span></a></li><li class="nav-item nav-level-6"><a class="nav-link" href="#%E9%81%8D%E5%8E%86%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88"><span class="nav-text">遍历数组的方案</span></a></li><li class="nav-item nav-level-6"><a class="nav-link" href="#%E5%88%A4%E6%96%AD%E5%85%83%E7%B4%A0%E6%98%AF%E6%95%B0%E7%BB%84%E7%9A%84%E6%96%B9%E6%A1%88"><span class="nav-text">判断元素是数组的方案</span></a></li><li class="nav-item nav-level-6"><a class="nav-link" href="#%E5%B0%86%E6%95%B0%E7%BB%84%E7%9A%84%E5%85%83%E7%B4%A0%E5%B1%95%E5%BC%80%E4%B8%80%E5%B1%82%E7%9A%84%E6%96%B9%E6%A1%88"><span class="nav-text">将数组的元素展开一层的方案</span></a></li><li class="nav-item nav-level-6"><a class="nav-link" href="#%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6-concat"><span class="nav-text">扩展运算符 +concat</span></a></li></ol></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E4%BA%8C%E9%97%AE%EF%BC%9A%E7%94%A8-reduce-%E5%AE%9E%E7%8E%B0-flat-%E5%87%BD%E6%95%B0"><span class="nav-text">第二问：用 reduce 实现 flat 函数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E4%B8%89%E9%97%AE%EF%BC%9A%E4%BD%BF%E7%94%A8%E6%A0%88%E7%9A%84%E6%80%9D%E6%83%B3%E5%AE%9E%E7%8E%B0-flat-%E5%87%BD%E6%95%B0"><span class="nav-text">第三问：使用栈的思想实现 flat 函数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E5%9B%9B%E9%97%AE%EF%BC%9A%E9%80%9A%E8%BF%87%E4%BC%A0%E5%85%A5%E6%95%B4%E6%95%B0%E5%8F%82%E6%95%B0%E6%8E%A7%E5%88%B6%E2%80%9C%E6%8B%89%E5%B9%B3%E2%80%9D%E5%B1%82%E6%95%B0"><span class="nav-text">第四问：通过传入整数参数控制“拉平”层数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E4%BA%94%E9%97%AE%EF%BC%9A%E4%BD%BF%E7%94%A8-Generator-%E5%AE%9E%E7%8E%B0-flat-%E5%87%BD%E6%95%B0"><span class="nav-text">第五问：使用 Generator 实现 flat 函数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E5%85%AD%E9%97%AE%EF%BC%9A%E5%AE%9E%E7%8E%B0%E5%9C%A8%E5%8E%9F%E5%9E%8B%E9%93%BE%E4%B8%8A%E9%87%8D%E5%86%99-flat-%E5%87%BD%E6%95%B0"><span class="nav-text">第六问：实现在原型链上重写 flat 函数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E7%AC%AC%E4%B8%83%E9%97%AE%EF%BC%9A%E8%80%83%E8%99%91%E6%95%B0%E7%BB%84%E7%A9%BA%E4%BD%8D%E7%9A%84%E6%83%85%E5%86%B5"><span class="nav-text">第七问：考虑数组空位的情况</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#%E6%89%A9%E5%B1%95%E9%98%85%E8%AF%BB%EF%BC%9A%E7%94%B1%E4%BA%8E%E7%A9%BA%E4%BD%8D%E7%9A%84%E5%A4%84%E7%90%86%E8%A7%84%E5%88%99%E9%9D%9E%E5%B8%B8%E4%B8%8D%E7%BB%9F%E4%B8%80%EF%BC%8C%E6%89%80%E4%BB%A5%E5%BB%BA%E8%AE%AE%E9%81%BF%E5%85%8D%E5%87%BA%E7%8E%B0%E7%A9%BA%E4%BD%8D%E3%80%82"><span class="nav-text">扩展阅读：由于空位的处理规则非常不统一，所以建议避免出现空位。</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%80%BB%E7%BB%93"><span class="nav-text">总结</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%9F%BA%E6%9C%AC%E5%AE%9E%E7%8E%B0"><span class="nav-text">基本实现</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8reduce%E7%AE%80%E5%8C%96"><span class="nav-text">使用reduce简化</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8"><span class="nav-text">直接调用</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F"><span class="nav-text">正则表达式</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%80%E5%80%BC"><span class="nav-text">最值</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#reduce"><span class="nav-text">reduce</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Math-max"><span class="nav-text">Math.max</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8reduce%E5%AE%9E%E7%8E%B0map"><span class="nav-text">使用reduce实现map</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8reduce%E5%AE%9E%E7%8E%B0filter"><span class="nav-text">使用reduce实现filter</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#7-%E6%95%B0%E7%BB%84%E4%B9%B1%E5%BA%8F-%E6%B4%97%E7%89%8C%E7%AE%97%E6%B3%95"><span class="nav-text">7.数组乱序-洗牌算法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#8-%E5%87%BD%E6%95%B0%E6%9F%AF%E9%87%8C%E5%8C%96"><span class="nav-text">8.函数柯里化</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AE%9A%E4%B9%89"><span class="nav-text">定义</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ES6%E5%86%99%E6%B3%95"><span class="nav-text">ES6写法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E5%86%99%E6%B3%95%E7%89%88"><span class="nav-text">简单写法版</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#9-%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0JSONP"><span class="nav-text">9.手动实现JSONP</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%9F%E7%90%86-2"><span class="nav-text">原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0"><span class="nav-text">简单实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#10-%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0promise"><span class="nav-text">10.模拟实现promise</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8"><span class="nav-text">简单使用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%9F%BA%E7%A1%80%E7%89%88%E6%9C%AC"><span class="nav-text">基础版本</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#then%E6%96%B9%E6%B3%95"><span class="nav-text">then方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#catch%E6%96%B9%E6%B3%95"><span class="nav-text">catch方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#finally%E6%96%B9%E6%B3%95"><span class="nav-text">finally方法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E5%A4%9F%E7%94%A8%E7%89%88-1"><span class="nav-text">面试够用版</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#promise"><span class="nav-text">promise</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Promise-all"><span class="nav-text">Promise.all</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Promise-race"><span class="nav-text">Promise.race</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#11-%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0ES5%E7%BB%A7%E6%89%BF"><span class="nav-text">11.手动实现ES5继承</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%9F%E5%9E%8B%E7%BB%A7%E6%89%BF"><span class="nav-text">原型继承</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9E%84%E9%80%A0%E7%BB%A7%E6%89%BF"><span class="nav-text">构造继承</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%BB%84%E5%90%88%E7%BB%A7%E6%89%BF"><span class="nav-text">组合继承</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AF%84%E7%94%9F%E7%BB%84%E5%90%88%E7%BB%A7%E6%89%BF"><span class="nav-text">寄生组合继承</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AF%84%E7%94%9F%E7%BB%84%E5%90%88%E7%BB%A7%E6%89%BF%E4%BC%98%E5%8C%96"><span class="nav-text">寄生组合继承优化</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#12-%E6%89%8B%E5%8A%A8%E5%AE%9E%E7%8E%B0instanceof"><span class="nav-text">12.手动实现instanceof</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8E%9F%E7%90%86-3"><span class="nav-text">原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0"><span class="nav-text">代码实现</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#13-%E5%9F%BA%E4%BA%8EPromise%E7%9A%84ajax%E5%B0%81%E8%A3%85"><span class="nav-text">13.基于Promise的ajax封装</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#14-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F"><span class="nav-text">14.单例模式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#15-%E5%BC%82%E6%AD%A5%E5%BE%AA%E7%8E%AF%E6%89%93%E5%8D%B0"><span class="nav-text">15.异步循环打印</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#16-%E5%9B%BE%E7%89%87%E6%87%92%E5%8A%A0%E8%BD%BD"><span class="nav-text">16.图片懒加载</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%9B%91%E5%90%AC%E5%9B%BE%E7%89%87%E9%AB%98%E5%BA%A6"><span class="nav-text">监听图片高度</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#IntersectionObserver"><span class="nav-text">IntersectionObserver</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#17-%E6%A8%A1%E6%8B%9FObject-create"><span class="nav-text">17.模拟Object.create</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#18-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAJSON-stringify"><span class="nav-text">18.实现一个JSON.stringify</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#19-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAJSON-parse"><span class="nav-text">19.实现一个JSON.parse</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%96%B9%E6%B3%951%EF%BC%9A%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8-eval"><span class="nav-text">方法1：直接调用 eval</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%96%B9%E6%B3%952%EF%BC%9AFunction"><span class="nav-text">方法2：Function</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#20-%E8%A7%A3%E6%9E%90-URL-Params-%E4%B8%BA%E5%AF%B9%E8%B1%A1"><span class="nav-text">20.解析 URL Params 为对象</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#21-%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E%E5%AE%9E%E7%8E%B0"><span class="nav-text">21.模板引擎实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%AE%9E%E7%8E%B0%E6%80%9D%E8%B7%AF-1"><span class="nav-text">实现思路</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%B8%80%E7%89%88-1"><span class="nav-text">第一版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Function"><span class="nav-text">Function</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%BA%8C%E7%89%88"><span class="nav-text">第二版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#with"><span class="nav-text">with</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%B8%89%E7%89%88"><span class="nav-text">第三版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E5%9B%9B%E7%89%88"><span class="nav-text">第四版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%8D%E6%96%9C%E6%9D%A0%E7%9A%84%E4%BD%9C%E7%94%A8"><span class="nav-text">反斜杠的作用</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97"><span class="nav-text">转义序列</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Line-Terminators"><span class="nav-text">Line Terminators</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Function-1"><span class="nav-text">Function</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6"><span class="nav-text">特殊字符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#replace"><span class="nav-text">replace</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%88%9B%E5%BB%BA"><span class="nav-text">正则表达式的创建</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6"><span class="nav-text">正则表达式的特殊字符</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%83%B0%E6%80%A7%E5%8C%B9%E9%85%8D"><span class="nav-text">惰性匹配</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#template"><span class="nav-text">template</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%BA%94%E7%89%88-%E7%89%B9%E6%AE%8A%E5%AD%97%E7%AC%A6%E7%9A%84%E5%A4%84%E7%90%86"><span class="nav-text">第五版 - 特殊字符的处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E5%85%AD%E7%89%88-%E7%89%B9%E6%AE%8A%E5%80%BC%E7%9A%84%E5%A4%84%E7%90%86"><span class="nav-text">第六版 - 特殊值的处理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E7%AC%AC%E4%B8%83%E7%89%88"><span class="nav-text">第七版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%80%E7%BB%88%E7%89%88"><span class="nav-text">最终版</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E9%9D%A2%E8%AF%95%E7%AE%80%E5%8C%96%E7%89%88"><span class="nav-text">面试简化版</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#22-%E8%BD%AC%E5%8C%96%E4%B8%BA%E9%A9%BC%E5%B3%B0%E5%91%BD%E5%90%8D"><span class="nav-text">22.转化为驼峰命名</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#23-%E6%9F%A5%E6%89%BE%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E5%AD%97%E7%AC%A6%E5%92%8C%E4%B8%AA%E6%95%B0"><span class="nav-text">23.查找字符串中出现最多的字符和个数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#24-%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%9F%A5%E6%89%BE"><span class="nav-text">24.字符串查找</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9A%B4%E5%8A%9B%E8%A7%A3"><span class="nav-text">暴力解</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#KMP"><span class="nav-text">KMP</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#25-%E5%AE%9E%E7%8E%B0%E5%8D%83%E4%BD%8D%E5%88%86%E9%9A%94%E7%AC%A6"><span class="nav-text">25.实现千位分隔符</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-%E6%96%B9%E6%B3%95%E4%B8%80"><span class="nav-text">1.方法一</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-%E6%96%B9%E6%B3%95%E4%BA%8C"><span class="nav-text">2.方法二</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#3-%E6%96%B9%E6%B3%95%E4%B8%89"><span class="nav-text">3. 方法三</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#26-%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E6%98%AF%E7%94%B5%E8%AF%9D%E5%8F%B7%E7%A0%81"><span class="nav-text">26.判断是否是电话号码</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#27-%E9%AA%8C%E8%AF%81%E6%98%AF%E5%90%A6%E6%98%AF%E9%82%AE%E7%AE%B1"><span class="nav-text">27.验证是否是邮箱</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#28-%E7%94%A8ES5%E5%AE%9E%E7%8E%B0%E6%95%B0%E7%BB%84%E7%9A%84map%E6%96%B9%E6%B3%95"><span class="nav-text">28.用ES5实现数组的map方法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#29-%E7%94%A8ES5%E5%AE%9E%E7%8E%B0%E6%95%B0%E7%BB%84%E7%9A%84reduce%E6%96%B9%E6%B3%95"><span class="nav-text">29.用ES5实现数组的reduce方法</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#30-%E8%AF%B7%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA-add-%E5%87%BD%E6%95%B0%EF%BC%8C%E6%BB%A1%E8%B6%B3%E4%BB%A5%E4%B8%8B%E5%8A%9F%E8%83%BD"><span class="nav-text">30.请实现一个 add 函数，满足以下功能</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#31-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA-sleep-%E5%87%BD%E6%95%B0%EF%BC%8C%E6%AF%94%E5%A6%82-sleep-1000-%E6%84%8F%E5%91%B3%E7%9D%80%E7%AD%89%E5%BE%851000%E6%AF%AB%E7%A7%92"><span class="nav-text">31.实现一个 sleep 函数，比如 sleep(1000) 意味着等待1000毫秒</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#32-%E5%AE%9E%E7%8E%B0-5-add-3-minus-2-%E5%8A%9F%E8%83%BD"><span class="nav-text">32.实现 (5).add(3).minus(2) 功能</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#33-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%8F%8C%E5%90%91%E7%BB%91%E5%AE%9A"><span class="nav-text">33.实现一个双向绑定</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#defineProperty-%E7%89%88%E6%9C%AC"><span class="nav-text">defineProperty 版本</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#proxy-%E7%89%88%E6%9C%AC"><span class="nav-text">proxy 版本</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#34-Array-isArray-%E5%AE%9E%E7%8E%B0"><span class="nav-text">34.Array.isArray 实现</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#35-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%87%BD%E6%95%B0%E5%88%A4%E6%96%AD%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="nav-text">35.实现一个函数判断数据类型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#36-%E5%AE%9E%E7%8E%B0Event-event-bus"><span class="nav-text">36. 实现Event(event bus)</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#37-%E6%A8%A1%E6%8B%9Fnew"><span class="nav-text">37.模拟new</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#38-%E6%89%8B%E5%86%99%E5%AE%9E%E7%8E%B0set%E5%92%8Cmap"><span class="nav-text">38.手写实现set和map</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#1-Set"><span class="nav-text">1.Set</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#2-Map"><span class="nav-text">2.Map</span></a></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="hxy"
      src="/images/Robben.gif">
  <p class="site-author-name" itemprop="name">hxy</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">80</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">8</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">120</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/huxingyi1997" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;huxingyi1997" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:huxingyi1997@zju.edu.cn" title="E-Mail → mailto:huxingyi1997@zju.edu.cn" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-frog"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">hxy</span>
</div>

<div class="theme-info">
  <div class="powered-by"></div>
  <span class="post-count">博客全站共1039.2k字</span>
</div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="//cdn.jsdelivr.net/npm/lozad@1/dist/lozad.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : true,
      appId      : 'pQsO3ySbU4VtWN2j1FLA74Ha-gzGzoHsz',
      appKey     : 'QYacMDY2VY7Wazprg1X6FiUv',
      placeholder: "Just go go",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : false,
      lang       : 'zh-cn' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

  
  <!-- 动态背景特效 -->
  <!-- 樱花特效 -->
    <script async src="/js/src/sakura.js"></script>
    <script async src="/js/src/fairyDustCursor.js"></script>
</body>
</html>
